Backend Development 7 min read

How to Dynamically Update Spring Cloud Zuul Static Routes with Nacos

Learn how to configure Spring Cloud Zuul to load static routing rules from Alibaba Nacos without restarting the service, by extending SimpleRouteLocator, creating a custom RefreshableRouteLocator, and wiring beans and configuration files for real‑time route updates.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Dynamically Update Spring Cloud Zuul Static Routes with Nacos

Environment: springboot2.2.10.RELEASE + springcloud Hoxton.SR3 + alibaba cloud 2.2.3.RELEASE

Alibaba Cloud uses the Nacos configuration center.

Zuul static route configuration in Spring Cloud is as follows:

<code>zuul:
  routes:
    api-a-url:
      path: /api-a-url/**
      #url: http://localhost:9002/</code>

Usually routes are defined in configuration files and require a restart to take effect. This article shows how to change static routes dynamically without restarting.

In Zuul, routing is performed by the RouteLocator class. The source code reveals the ZuulServerAutoConfiguration class which defines two beans, including CompositeRouteLocator that aggregates other locators.

SimpleRouteLocator is a basic locator with @ConditionalOnMissingBean, allowing us to provide our own implementation by extending it.

Key methods in SimpleRouteLocator:

Method that retrieves all route information.

protected getRoutesMap() – intended for subclass overrides.

locateRoutes() – obtains route definitions from ZuulProperties.

By extending SimpleRouteLocator we can merge the original routes with custom routes loaded from Nacos.

<code>public abstract class CustomizeRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
    public CustomizeRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
    }

    @Override
    public void refresh() {
        doRefresh();
    }

    @Override
    protected Map<String, ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
        routesMap.putAll(super.locateRoutes());
        routesMap.putAll(customizeRoute());
        return routesMap;
    }

    protected abstract Map<String, ZuulRoute> customizeRoute();
}</code>

The locateRoutes method combines the routes from the parent class with those returned by customizeRoute().

<code>routesMap.putAll(super.locateRoutes()); // call parent method
routesMap.putAll(customizeRoute()); // custom implementation</code>

Implementation of the custom locator:

<code>public class NacosRouteLocator extends CustomizeRouteLocator {
    private static final Logger logger = LoggerFactory.getLogger(NacosRouteLocator.class);
    private ZuulProperties properties;
    @Resource
    private CustomizeRoutes routes;

    public NacosRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
        this.properties = properties;
    }

    @Override
    protected Map<String, ZuulRoute> customizeRoute() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
        for (Entry<String, ZuulRoute> entry : routes.getRoutes().entrySet()) {
            entry.getValue().setId(entry.getKey());
            routesMap.put(entry.getValue().getPath(), entry.getValue());
        }
        LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
        for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (StringUtils.hasText(this.properties.getPrefix())) {
                path = this.properties.getPrefix() + path;
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }
            values.put(path, entry.getValue());
        }
        logger.info("Custom static route, dynamic config: {}", values);
        return values;
    }
}</code>

The class reads route definitions from Nacos. The @RefreshScope annotation on the configuration bean ensures that changes in Nacos are refreshed at runtime.

<code>@Configuration
public class CustomizeRouteConfig {
    @Resource
    private ServerProperties server;
    @Resource
    private ZuulProperties zuulProperties;

    @Bean
    public CustomizeRouteLocator customizeRouteLocator() {
        return new NacosRouteLocator(this.server.getServlet().getContextPath(),
                this.zuulProperties);
    }
}</code>
<code>@Configuration
@ConfigurationProperties
@RefreshScope
public class CustomizeRoutes {
    private Map<String, ZuulRoute> routes;

    public Map<String, ZuulRoute> getRoutes() {
        return routes;
    }

    public void setRoutes(Map<String, ZuulRoute> routes) {
        this.routes = routes;
    }
}</code>

bootstrap.yml configures the Spring Cloud Nacos client, registers the application name and environment (dev), and enables configuration fetching.

In Nacos, a yaml file (cloud-gateway-dev.yaml) defines the custom routes. Because the beans are annotated with @RefreshScope, the gateway picks up changes instantly without a restart.

Testing confirms that routes are applied in real time.

JavaMicroservicesNacosSpring CloudDynamic RoutingZuul
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.