WMS GetCapabilities doesn't "like" namespace param in GeoServer 2.28.2

In an attempt to upgrade my GeoServer from 2.19.3 to 2.28.2, I have been testing the newer one by using a dockerized version:

One of the breaking changes I have come up to is that the following request:
/geoserver/wms?service=wms&version=1.3.0&request=GetCapabilities&namespace=mynamespace
That used to work in 2.19.3 now returns an InvalidParameterValue exception for namespace with the following message:
Illegal namespace declaration, should be of the form xmlns(<prefix>=<ns uri>): mynamespace

Is this something expected? I have tried to format the namespace param as suggested, but although I get no exception, the param filter is not applied. Actually I get no Layers in the response, no matter how I tried to format the param value.

The only thing that worked, is to rename namespacenamespaces after I found this ticket

Now I am not sure why that works in 2.28.2, but not in 2.19.3, and of course why the original documented param name: namespace throws that exception in 2.28.2.
Note that if I omit the param, the request works in 2.28.2, but it of course doesn’t filter the Layers by namespace, but returns all layers from all namespaces.

Does anyone know if there’s a bug or a breaking change that should explain this behavior?

Thanks in advance!

Yes, this is an expected (though highly frustrating and poorly documented) breaking change introduced during the major core architecture upgrades between GeoServer 2.19.x and newer versions like 2.28.x.

Here is exactly what is happening under the hood and why your workaround behaves this way:

1. The Strict OGC Compliance Shift

In older versions (2.19.3), GeoServer used a loose custom parser for the namespace parameter in GetCapabilities requests. It simply filtered the workspace layers by the string prefix.

In modern versions, GeoServer strictly enforces the XML Namespace specifications. The namespace parameter is now strictly parsed as an explicit XML namespace declaration format. That’s why it throws the Illegal namespace declaration exception unless you pass it exactly like this: namespace=xmlns(mynamespace=http://your-workspace-uri).

If you format it correctly but get zero layers, it means the URI inside the xmlns() bracket doesn’t perfectly match the Namespace URI configured in your GeoServer Workspace settings.

2. Why namespaces (plural) works as a workaround

The reason namespaces=mynamespace works is due to a backward-compatibility patch implemented in GeoServer’s dispatching layer (as you saw in the Jira ticket). When the multi-value namespaces filter is hit, GeoServer bypasses the strict xmlns validation rule and falls back to filtering the catalog by workspace names.

However, if you are migrating a production stack using the Kartoza Docker image, relying on the namespaces string parameter can cause unexpected caching issues if you are running behind an Ingress proxy (like Nginx or Traefik) or utilizing GeoWebCache (GWC), because GWC has strict parameter filters configured for namespace (singular) by default.

Modern Recommendation for Workspaces

If you want to isolate capabilities cleanly per client/workspace without breaking URL parameters, the production-grade standard now is to use Virtual OWS (Open Web Services) endpoints instead of global parameters.

Instead of hitting the global URL: /geoserver/wms?service=wms&...&namespace=mynamespace

You should isolate requests directly via the virtual workspace path: /geoserver/mynamespace/wms?service=wms&version=1.3.0&request=GetCapabilities

This completely eliminates parameter parsing exceptions, forces GeoServer to return only the layers belonging to mynamespace, and is perfectly cached by Docker reverse proxies.

Migrating legacy GIS stacks (especially jumping across 9+ minor versions like 2.19 to 2.28) inside containerized environments often surfaces these strict routing, Java engine, and database catalog mismatches.

If your team is looking to streamline this migration—whether it’s refactoring your automated Python ETL pipelines to match the new OGC specs, debugging memory leaks inside Kartoza Docker clusters, or setting up robust production deployments—feel free to drop me a line. I specialize in high-performance Python backends, GIS automation, and Docker infrastructure optimizations. I run a dedicated DevOps and script configuration service on Fiverr to help teams handle tricky version transitions seamlessly: Dockerize your python apps and setup automated pipelines by Kamedashe | Fiverr.

Thank you for this detailed answer. I would have used the virtual OWS endpoints, but this required updating all clients, both for the request part, and for the response, since the responses do not include the namespace prefix (e.g. mynamspace:mylayer) in Layer names, href(s) etc, and existing logic was relying on these.

So to avoid a lot of refactoring, I tried to use the namespace=xmlns(mynamespace=http://your-workspace-uri) format, but as I said it didn’t work.
Now my workspace settings looks like this:

Can you spot anything weird that would prevent this filter from working :
namespace=xmlns(mynamespace=http://localhost:9000/mynamespace)
Could it be that the filter tries to actually resolve the namespace URI and fails, because of the internal docker routing for example?

Thanks in advance!

Glad it helped! Virtual OWS endpoints are definitely the cleanest way to handle this, especially when running behind Nginx in a containerized setup.

You don’t actually need to change any complex code inside GeoServer itself to enable Virtual Endpoints; GeoServer supports them out of the box for any workspace you create. The real trick is configuring Nginx to route the clean proxy paths correctly and ensuring GeoServer knows its public face.

Here is a production-grade pattern you can use right now:

1. Configure Nginx Reverse Proxy

You need to make sure Nginx passes the original host headers and handles the sub-paths properly so GeoServer doesn’t get confused about internal Docker network IPs. Add this location block to your Nginx configuration:

location /geoserver {
proxy_pass http://geoserver:8080/geoserver; # Matches your Kartoza container name/port
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Crucial for stable OGC proxying
proxy_redirect off;
proxy_buffers 16 16k;
proxy_buffer_size 32k;

}

2. Update GeoServer Global Settings (Crucial Step!)

If you don’t do this, GeoServer will generate internal XML capabilities links pointing to http://geoserver:8080/... (the internal Docker network) instead of your public domain.

  1. Log into your GeoServer Admin Dashboard.

  2. Go to Global Settings.

  3. Find the Proxy Base URL field and set it to your public facing URL (e.g., https://yourdomain.com/geoserver).

  4. Check the box for Enable Global Service Status.

Once this is done, hitting https://yourdomain.com/geoserver/mynamespace/wms?service=wms&version=1.3.0&request=GetCapabilities will return perfectly filtered layers, and Nginx will handle the caching flawlessly.

If your migration team runs into complex automated script refactoring, memory leaks under concurrent Java/Docker loads, or you need a turnkey configuration for your entire Nginx/Kartoza stack setup, feel free to send me a direct message here or drop a line via my active DevOps layout on Fiverr: Dockerize your python apps and setup automated pipelines by Kamedashe | Fiverr. I’d be happy to take a closer look at your infrastructure environment.

Good luck with the upgrade!

1 Like

While virtual OWS endpoints are already setup on the containerized GeoServer I have been experimenting with, the WMS/WMTS responses of course lack the namespace prefix in the attribute values like Layer Names e.g. mylayername VS mynamespace:mylayername.

This breaks several client dependencies and I wish I could avoid refactoring the clients. I will of course do it if necessary, but I still don’t understand why the filtering doesn’t work.

I would love to see a working example of someone’s setup that uses namespace filtering in GeoServer 2.28.2 (or above), using the namespace=xmlns(mynamespace=http://your-workspace-uri) format.

I’ll wait for a few days for any feedback, and then if no other answer works, I will mark @kamedashe ‘s suggestion as solution.
Thanks again!