What is the difference between /* and /** pattern in Spring boot?

Spring normally uses Ant-style path matching patterns for URLs. If you look at the Java docs for the AntPathMatcher you will see the explanation:

The mapping matches URLs using the following rules:

  • ? matches one character
  • * matches zero or more characters
  • ** matches zero or more directories in a path
  • {spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"

there is no /** in registration.addUrlPatterns as we can see in Source code

private static boolean matchFiltersURL(String testPath, String requestPath) {

    if (testPath == null)
        return false;

    // Case 1 - Exact Match
    if (testPath.equals(requestPath))
        return true;

    // Case 2 - Path Match ("/.../*")
    if (testPath.equals("/*"))
        return true;
    if (testPath.endsWith("/*")) {
        if (testPath.regionMatches(0, requestPath, 0,
                                   testPath.length() - 2)) {
            if (requestPath.length() == (testPath.length() - 2)) {
                return true;
            } else if ('/' == requestPath.charAt(testPath.length() - 2)) {
                return true;
            }
        }
        return false;
    }

    // Case 3 - Extension Match
    if (testPath.startsWith("*.")) {
        int slash = requestPath.lastIndexOf('/');
        int period = requestPath.lastIndexOf('.');
        if ((slash >= 0) && (period > slash)
            && (period != requestPath.length() - 1)
            && ((requestPath.length() - period)
                == (testPath.length() - 1))) {
            return testPath.regionMatches(2, requestPath, period + 1,
                                           testPath.length() - 2);
        }
    }

    // Case 4 - "Default" Match
    return false; // NOTE - Not relevant for selecting filters

}

enter image description here


IMHO code worths 100 words in this case:

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.springframework.util.AntPathMatcher

class AntPathMatcherTests {
    @Test
    fun twoAsterisks() {
        val pattern = "/api/balance/**"

        val matcher = AntPathMatcher()
        val matching = { path: String -> matcher.match(pattern, path) }

        assertTrue(matching("/api/balance"))
        assertTrue(matching("/api/balance/"))
        assertTrue(matching("/api/balance/abc"))
        assertTrue(matching("/api/balance/abc/"))
        assertTrue(matching("/api/balance/abc/update"))

        assertFalse(matching("/api/bala"))
    }

    @Test
    fun oneAsterisk() {
        val pattern = "/api/balance/*"

        val matcher = AntPathMatcher()
        val matching = { path: String -> matcher.match(pattern, path) }

        assertTrue(matching("/api/balance/"))
        assertTrue(matching("/api/balance/abc"))

        assertFalse(matching("/api/bala"))
        assertFalse(matching("/api/balance"))
        assertFalse(matching("/api/balance/abc/"))
        assertFalse(matching("/api/balance/abc/update"))
    }
}