With the structure you describe, there is no way to get the desired injection.
The EJB classloader will never be able to access classes inside the WAR, so the injection will never consider an alternative implementation.
The solution is possible if you want to change the structure of the EAR by placing the alternative (D) in lib / jar along with appropriare beans.xml . Class D will become visible to your EJB and your WAR, and the injection went as desired.
EDIT
The solution you posted that I describe here almost works.
EAR - ejb-module-1.jar - A.class (@Inject I) - I.class - C.class (@Stateless implements I) - META-INF/beans.xml - ejb-module-2.jar - D.class (@Alternative @Stateless implements I) - META-INF/beans.xml (<alternatives><class>D</class></alternative>) - app.war - calls A.test() - WEB-INF/beans.xml
The only catch is that you missed out on the beans.xml alternate declaration.
The CDI specification (1.1, but also applicable to the previous implementation) states in chapter 5.1 that:
An alternative is not available for injecting, searching, or resolving EL to JSP / JSF classes or pages in a module unless the module is a bean archive and the alternative is explicitly chosen in that bean archive.
In other words, you must select an alternative in the same module of the class that uses the bean.
Here is the revised (and working) structure:
EAR - ejb-module-1.jar - A.class (@Inject I) - I.class - C.class (@Stateless implements I) - META-INF/beans.xml (<alternatives><class>D</class></alternative>) - ejb-module-2.jar - D.class (@Alternative @Stateless implements I) - META-INF/beans.xml (empty <beans></beans>) - app.war - calls A.test() - WEB-INF/beans.xml (empty <beans></beans>)
Also remember that although for standard beans the alternative choice only works for the in in witch module, the alternative is declared in beans.xml , the same does not apply to EJB. So your alternative to D , (being @Stateless ) is valid for the whole application.