@@ -605,6 +605,37 @@ private static void runFrontendBuildTool(PluginAdapterBase adapter,
605605 }
606606 }
607607
608+ /**
609+ * Validate pro component licenses.
610+ * <p>
611+ * When {@link PluginAdapterBuild#optimizeBundle()} is {@code false} the
612+ * bytecode scanner is disabled and every commercial component present on
613+ * the classpath is assumed to be used, regardless of whether the
614+ * application actually references it. In that case the thrown
615+ * {@link LicenseException} uses a dedicated message that explains the
616+ * classpath-level detection and suggests concrete workarounds (re-enable
617+ * bundle optimization, switch to {@code com.vaadin:vaadin-core}, or exclude
618+ * {@code com.vaadin:vaadin} from a transitive dependency).
619+ *
620+ * @param adapter
621+ * the PluginAdapterBuild
622+ * @param frontendDependencies
623+ * frontend dependencies scanner
624+ * @return {@literal true} if license validation is required because of the
625+ * presence of commercial components, otherwise {@literal false}.
626+ * @throws MissingLicenseKeyException
627+ * if commercial components are used in a commercial
628+ * banner-enabled build and no license key is present
629+ * @throws LicenseException
630+ * if commercial components are used without a license and
631+ * commercial banner is not enabled
632+ */
633+ public static boolean validateLicenses (PluginAdapterBuild adapter ,
634+ FrontendDependenciesScanner frontendDependencies ) {
635+ return doValidateLicenses (adapter , frontendDependencies ,
636+ adapter .optimizeBundle ());
637+ }
638+
608639 /**
609640 * Validate pro component licenses.
610641 *
@@ -620,9 +651,25 @@ private static void runFrontendBuildTool(PluginAdapterBase adapter,
620651 * @throws LicenseException
621652 * if commercial components are used without a license and
622653 * commercial banner is not enabled
654+ * @deprecated use
655+ * {@link #validateLicenses(PluginAdapterBuild, FrontendDependenciesScanner)}
656+ * instead
623657 */
658+ @ Deprecated (since = "25.2" , forRemoval = true )
624659 public static boolean validateLicenses (PluginAdapterBase adapter ,
625660 FrontendDependenciesScanner frontendDependencies ) {
661+ if (adapter instanceof PluginAdapterBuild pluginAdapterBuild ) {
662+ return validateLicenses (pluginAdapterBuild , frontendDependencies );
663+ }
664+ // Fallback for callers that only provide a PluginAdapterBase: the
665+ // optimizeBundle property is not accessible, so assume the default
666+ // (true) and emit the original error message.
667+ return doValidateLicenses (adapter , frontendDependencies , true );
668+ }
669+
670+ private static boolean doValidateLicenses (PluginAdapterBase adapter ,
671+ FrontendDependenciesScanner frontendDependencies ,
672+ boolean optimizeBundle ) {
626673 File outputFolder = adapter .frontendOutputDirectory ();
627674
628675 String statsJsonContent = null ;
@@ -679,20 +726,8 @@ public static boolean validateLicenses(PluginAdapterBase adapter,
679726 .formatted (productsList ));
680727 }
681728 invalidateOutput (component , outputFolder );
682- throw new LicenseException (String .format (
683- """
684- Commercial features require a subscription.
685- Your application contains the following commercial components and no license was found:
686- %1$s
687-
688- If you have an active subscription, please download the license key from https://vaadin.com/myaccount/licenses.
689- Otherwise go to https://vaadin.com/pricing to obtain a license.
690-
691- You can also build a watermarked version of the application configuring
692- the '%2$s' property of the Maven or Gradle plugin
693- or run the build with the '-Dvaadin.%2$s' system parameter
694- """ ,
695- productsList , InitParameters .COMMERCIAL_WITH_BANNER ));
729+ throw new LicenseException (
730+ buildLicenseErrorMessage (productsList , optimizeBundle ));
696731 } catch (Exception e ) {
697732 invalidateOutput (component , outputFolder );
698733 throw e ;
@@ -701,6 +736,66 @@ public static boolean validateLicenses(PluginAdapterBase adapter,
701736 return !commercialComponents .isEmpty ();
702737 }
703738
739+ private static String buildLicenseErrorMessage (String productsList ,
740+ boolean optimizeBundle ) {
741+ if (optimizeBundle ) {
742+ return String .format (
743+ """
744+ Commercial features require a subscription.
745+ Your application contains the following commercial components and no license was found:
746+ %1$s
747+
748+ If you have an active subscription, please download the license key from https://vaadin.com/myaccount/licenses.
749+ Otherwise go to https://vaadin.com/pricing to obtain a license.
750+
751+ You can also build a watermarked version of the application configuring
752+ the '%2$s' property of the Maven or Gradle plugin
753+ or run the build with the '-Dvaadin.%2$s' system parameter
754+ """ ,
755+ productsList , InitParameters .COMMERCIAL_WITH_BANNER );
756+ }
757+ return String .format (
758+ """
759+ Commercial features require a subscription.
760+ The following commercial components were detected on the classpath and no license was found:
761+ %1$s
762+
763+ If you have an active subscription, please download the license key from https://vaadin.com/myaccount/licenses.
764+ Otherwise go to https://vaadin.com/pricing to obtain a license.
765+
766+ You can also build a watermarked version of the application configuring
767+ the '%2$s' property of the Maven or Gradle plugin
768+ or run the build with the '-Dvaadin.%2$s' system parameter.
769+
770+ Note: bundle optimization is disabled ('optimizeBundle=false'), so the bytecode scanner that
771+ normally restricts detection to components actually referenced by the application is bypassed
772+ — every commercial component present on the classpath is assumed to be in use.
773+
774+ If the application does not actually use these commercial components, the likely cause is a
775+ dependency on the 'com.vaadin:vaadin' umbrella artifact, which transitively includes every
776+ commercial component. You can resolve this by:
777+
778+ 1. Setting the 'optimizeBundle' property of the Vaadin plugin to 'true' (for example in the
779+ vaadin-maven-plugin configuration in pom.xml, the 'vaadin { }' block in build.gradle(.kts),
780+ or the corresponding entry in application.properties for Quarkus). Bytecode scanning will
781+ then restrict detection to components actually referenced by the application.
782+
783+ 2. If your project declares 'com.vaadin:vaadin' as a direct dependency, replacing it
784+ with 'com.vaadin:vaadin-core'.
785+
786+ 3. If 'com.vaadin:vaadin' is pulled in transitively (for example by an add-on), identify the
787+ responsible dependency by running:
788+ Maven: mvn dependency:tree -Dincludes=com.vaadin:vaadin
789+ Gradle: ./gradlew dependencyInsight --configuration runtimeClasspath --dependency com.vaadin:vaadin:
790+ (note the trailing colon after 'vaadin' — it narrows the match to the exact
791+ umbrella artifact and excludes 'com.vaadin:vaadin-*' sub-artifacts)
792+ Then exclude 'com.vaadin:vaadin' from that dependency.
793+ Please also report the issue to the add-on author — add-ons should declare 'com.vaadin:vaadin' with 'provided' scope
794+ in Maven or 'compileOnly' in Gradle so it is never leaked transitively to consumers.
795+ """ ,
796+ productsList , InitParameters .COMMERCIAL_WITH_BANNER );
797+ }
798+
704799 private static void invalidateOutput (Product component , File outputFolder ) {
705800 try {
706801 getLogger ().debug (
0 commit comments