Every time I restart my Spring Boot service during development, I repeat the same ritual:
- Wait for startup to finish.
- Scroll the console to find the Swagger URL.
- Copy it.
- Paste it into the browser.
A hundred times a day. So I automated it.
In this post I'll show a small CommandLineRunner bean that auto-opens Swagger UI in your default browser, works identically on Linux, Windows, and macOS, and fails gracefully on headless servers (Docker, CI, remote VMs).
The whole thing is ~60 lines of pure JDK code. No extra dependencies.
What it does
- Detects the OS at runtime via
System.getProperty("os.name") - Uses
xdg-openon Linux,openon macOS, andcmd /c starton Windows - Falls back to
java.awt.Desktop#browseif the CLI tool is missing - Silently skips on headless machines (no crashes)
- Toggleable via
application.yml— setopenProject.isOpen: falsein prod - Prints a clickable link in the console for good measure
1. Configuration
Add a small block to application.yml:
openProject:
isOpen: true
web:
openUrl: "http://localhost:8080/swagger-ui/index.html"
2. The BrowserLauncher bean
package com.gama.swagger.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.awt.Desktop;
import java.net.URI;
import java.util.Locale;
@Component
public class BrowserLauncher implements CommandLineRunner {
private static final Logger log = LoggerFactory.getLogger(BrowserLauncher.class);
@Value("${openProject.isOpen:true}")
private boolean isOpen;
@Value("${openProject.web.openUrl}")
private String openUrl;
@Override
public void run(String... args) {
if (!isOpen) {
log.info("Auto-open browser disabled (openProject.isOpen=false).");
return;
}
String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
log.info("Detected OS: {}", os);
try {
if (os.contains("win")) {
// Windows: cmd /c start "" <url> (the empty "" is the window title)
Runtime.getRuntime().exec(new String[]{"cmd", "/c", "start", "", openUrl});
} else if (os.contains("mac")) {
Runtime.getRuntime().exec(new String[]{"open", openUrl});
} else if (os.contains("nux") || os.contains("nix") || os.contains("aix")) {
if (isCommandAvailable("xdg-open")) {
Runtime.getRuntime().exec(new String[]{"xdg-open", openUrl});
} else {
log.warn("xdg-open not found. Install it: sudo apt install xdg-utils");
fallbackDesktopBrowse();
}
} else {
log.warn("Unrecognized OS '{}' — trying Desktop.browse fallback", os);
fallbackDesktopBrowse();
}
log.info("Attempted to open browser: {}", openUrl);
} catch (Exception e) {
log.warn("Could not auto-open browser: {}", e.getMessage());
}
}
/** Last-resort attempt using the Java Desktop API (works when a GUI is present). */
private void fallbackDesktopBrowse() {
try {
if (Desktop.isDesktopSupported()
&& Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(new URI(openUrl));
log.info("Opened via Desktop.browse fallback.");
} else {
log.warn("No GUI available — please open manually: {}", openUrl);
}
} catch (Exception ex) {
log.warn("Desktop.browse fallback failed: {}", ex.getMessage());
}
}
private boolean isCommandAvailable(String cmd) {
try {
Process p = new ProcessBuilder("which", cmd)
.redirectErrorStream(true)
.start();
return p.waitFor() == 0;
} catch (Exception e) {
return false;
}
}
}
3. Print a clickable link in the console
Most modern terminals (GNOME Terminal, Konsole, iTerm2, Windows Terminal, IntelliJ's console, VS Code's terminal) auto-detect URLs. Just log the full URL from main:
@SpringBootApplication
public class SwaggerApplication {
private static final Logger log = LoggerFactory.getLogger(SwaggerApplication.class);
public static void main(String[] args) {
var context = SpringApplication.run(SwaggerApplication.class, args);
String port = context.getEnvironment().getProperty("server.port", "8080");
String ctx = context.getEnvironment().getProperty("server.servlet.context-path", "");
log.info("Swagger UI: http://localhost:{}{}/swagger-ui/index.html", port, ctx);
}
}


Top comments (0)