Why does EK9 reject two 'register' lines for the same abstract component type?

← DI Validation · Ref: Q1334

Within a single application block each abstract component type may have exactly ONE concrete registration. A second 'register ... as <SameAbstractType>' raises E08230 (duplicate registration). The reason is resolution: when a program injects 'cache as Cache!', the compiler must bind that injection point to exactly one concrete implementation. Two registrations for Cache would make that binding ambiguous, so EK9 rejects it at compile time rather than picking arbitrarily or failing at runtime.

The fix is to keep one binding per abstract type in the application. If you genuinely need a different implementation (production vs test), declare a SEPARATE application, each with its own single registration, and select the application with 'with application of'.

See Q673 for missing registration (E08210). See Q674 for the same rule from the ambiguity angle. See Q671 for circular dependencies.

Example

defines module qa.divalidation.duplicateregistration

  defines component

    <?-
      Abstract cache contract.
    -?>
    Cache as abstract

      store() as abstract
        ->
          key as String
          payload as String

      default operator ?

    <?-
      Redis-backed cache for production.
    -?>
    RedisCache extends Cache

      override store()
        ->
          key as String
          payload as String
        stdout <- Stdout()
        stdout.println(`Redis store ${key}=${payload}`)

      default operator ?

    <?-
      In-memory cache for tests.
    -?>
    MemoryCache extends Cache

      override store()
        ->
          key as String
          payload as String
        stdout <- Stdout()
        stdout.println(`Memory store ${key}=${payload}`)

      default operator ?

  defines application

    <?-
      Production wiring: exactly ONE registration for Cache.
    -?>
    ProdApp
      register RedisCache() as Cache

    <?-
      Test wiring: same abstract type, different implementation,
      in a SEPARATE application. One binding each, no E08230.
    -?>
    TestApp
      register MemoryCache() as Cache

  defines program

    DuplicateRegistrationDemo() with application of ProdApp
      stdout <- Stdout()

      cache as Cache!
      cache.store("user:1", "Alice")

      stdout.println("One abstract type = one registration per application")

Common mistakes

E08230 — Both 'register' lines bind the same abstract type Cache inside one application, so an injection point 'cache as Cache!' has two candidate implementations and cannot be resolved unambiguously - this raises E08230. Keep one registration per abstract type per application; if you need a different implementation, put it in a separate application (ProdApp vs TestApp) and select it with 'with application of'. See ek9 -h E08230 for details.

Incorrect:

BrokenApp
      register RedisCache() as Cache
      register MemoryCache() as Cache

Correct:

    ProdApp
      register RedisCache() as Cache

    <?-
Other ways to ask this
  • What triggers E08230 duplicate registration in an application?
  • Why can I only register one implementation per abstract type per application?
  • How do I wire two implementations of one interface in EK9?

Coming from another language?

Java/Spring: two beans of one type need @Primary or @Qualifier, otherwise NoUniqueBeanDefinitionException at startup (runtime). Guice: a duplicate bind throws CreationException at injector build time. Kotlin/Koin: last definition silently overrides earlier ones. EK9: a duplicate registration for the same abstract type in one application is a compile-time error (E08230); use separate applications for separate wiring.

Keywords: registration, component, register, DI, binding, E08230, application, inject, ambiguous, duplicate, abstract