The correct solution is to use the <packagingExcludes> configuration, as the <scope>provided</scope> solution is a hack.
Consider the following multi-module project:
A
(war)
/ \
B C
(jar) (jar)
/ /
D /
(jar) /
/ | \ /
e f g
In this multi-module project, module A requires modules {B, C, D}, but not {e, f, g}. However, modules B and D do require {e, f g}, and C requires {g}.
First, let's try to solve this problem with the <scope>provided</scope> approach:
To exclude {e, f, g} from A, the <scope>provided</scope> spec must be present in D's POM. But wait, B requires {e, f, g}. So to fix that, the dependency declarations for {e, f, g} must be present in B's POM as well (with <scope>provided</scope>). This means that the dependency spec complexity from D must be been pulled into B. Similarly, since C depends on {g}, the dependency declarations for {g} must be present in C's POM.
With the the <scope>provided</scope> solution, modules B, C, and D must all have knowledge of the fact that A cannot have {e, f, g}.
This approach breaks the encapsulation principle, and is not practical in multi-module projects that have complex dependency graphs.
Second, let's try to solve this problem with the <packagingExcludes> approach:
To exclude {e, f, g} from A, one must provide the following in A's POM:
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<packagingExcludes>B*.jar,C*.jar</packagingExcludes>
</configuration>
</plugin>
With this solution, the complexity of "what must be excluded from A" is contained solely in A's POM. This frees modules B, C and D from having to care about excluding transitive dependencies for the sake of A.
This solution upholds the encapsulation principle.