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); } } 

