Why does EK9 reject two service methods that share the same HTTP path?

← Web Services · Ref: Q1305

Each HTTP verb plus URI path must map to exactly one handler method. When two methods on a service share the same verb and structurally identical path, EK9 raises E02070 at compile time - there is no way to know which handler should run for that URL.

Path parameters are compared STRUCTURALLY, not by name: :/{id} and :/{userId} are the SAME path, so renaming a path variable does NOT resolve the conflict. The fix is to make the paths genuinely distinct (a different literal segment, an extra segment, or a different HTTP verb).

See Q684 for valid URI path rules. See Q946 for path-variable-to-parameter matching.

Example

defines module qa.webdeep.duplicatepath

  defines constant

    JSON_CONTENT_TYPE <- "application/json"
    ENGLISH_LANGUAGE <- "en"

  defines service

    <?-
      Service demonstrating distinct endpoints that avoid E02070.
      Each HTTP verb plus path combination is unique. Note that
      :/{id} and :/{name}/profile differ structurally (extra segment),
      so they are NOT duplicates even though both contain a path variable.
    -?>
    UserService :/users open

      //GET /users/{id} — lookup by identifier
      findById() as GET for :/{id}
        -> id as String
        <- response as HTTPResponse: (capturedId: id) with trait HTTPResponse
          override content()
            <- rtn as String: `{"id": "${capturedId}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_CONTENT_TYPE
          override cacheControl() as pure
            <- rtn as String: "no-cache"
          override contentLanguage() as pure
            <- rtn as String: ENGLISH_LANGUAGE
          default operator ?

      //GET /users/{name}/profile — distinct path (extra literal segment),
      //so no E02070 conflict with findById even though both have a variable.
      byName() as GET for :/{name}/profile
        -> name as String
        <- response as HTTPResponse: (capturedName: name) with trait HTTPResponse
          override content()
            <- rtn as String: `{"name": "${capturedName}"}`
          override status() as pure
            <- rtn as Integer: 200
          override contentType() as pure
            <- rtn as String: JSON_CONTENT_TYPE
          override cacheControl() as pure
            <- rtn as String: "max-age=60"
          override contentLanguage() as pure
            <- rtn as String: ENGLISH_LANGUAGE
          default operator ?

  defines application

    UserApp
      register UserService()

  defines program

    DuplicateServicePathDemo()
      stdout <- Stdout()
      stdout.println("Distinct service endpoints (no E02070):")
      stdout.println("  GET /users/{id}")
      stdout.println("  GET /users/{name}/profile")

Common mistakes

E02070 — Two GET methods both mapping to :/{id} and :/{nick} are the SAME endpoint because path variables match structurally, not by name - so EK9 cannot pick a handler and raises E02070. Make the paths genuinely distinct (add a literal segment such as :/{name}/profile, or use a different HTTP verb). See ek9 -h E02070 for details.

Incorrect:

byNick() as GET for :/{nick}

Correct:

byName() as GET for :/{name}/profile
Other ways to ask this
  • What triggers E02070 duplicate service path/operation?
  • Why can't two GET methods map to the same URI in EK9?
  • How do I fix a duplicated service endpoint path in EK9?

Coming from another language?

Java Spring: two @GetMapping("/x") methods compile fine and only collide at startup with an AmbiguousMappingException - a runtime failure. Python Flask: a duplicate @app.route raises only when the app boots. Go: last route silently wins. EK9: the duplicate endpoint is a compile-time error (E02070), so the ambiguous routing can never ship.

Keywords: path, http, endpoint, URI, duplicate, E02070, GET, webservice, service, route