Broadleaf Microservices
  • v1.0.0-latest-prod

Catalog Browse Release Notes for 2.2.0-GA

Tip
The 2.x versions are Spring Boot 3 compatible.

Important Updates

Spring Boot Upgrade

  • As of Broadleaf Release Train 2.3.0-GA, all microservices have been upgraded to support Spring Boot 3.3 & 3.5.

Requirements

  • JDK 17 is required for Broadleaf release trains 2.0.0-GA, and beyond.

New Features & Notable Changes

Add Ehcache and Caffeine cache support

  • Added support for Ehcache and Caffeine caches for dynamic customer segments.

  • For more information on configuring these caches, see the Caching Configuration guide.

  • Accept a new skipInventory parameter in /browse/products (aka /products/details) to skip inventory availability checks. Internally, the backend flow already supported honoring ProductDetailsRequest.skipInventory, but this setting was not exposed via the API.

  • Updated the pricing logic to use product’s default frequency when doing initial product pricing (e.g. PDP and PLP views) for products with recurring pricing

Feature: Added mechanism to filter out sensitive data from browse product payloads

See the CartOperation-counterpart of this mechanism in Cart Operation Services 2.3.0 Release Notes.

Introduced an OncePerRequest filter guarded by configured URIs patterns that will remove the baseCost property from the CatalogBrowse response payloads.

This mechanism can be configured by the following properties and is enabled by default (see CatalogBrowseSensitiveDataRemovalProperties):

broadleaf:
  catalogbrowse:
    sensitive-data-removal:
      enabled: true
      url-patterns:
        - /browse/*
        - /products/details
        - ... etc.
      fields-to-remove:
        - cost          # default
        - baseCost      # default

Feature: Variant Pricing Strategy

Important
Requires Catalog Services 2.3.0

Feature: Variant Pricing Strategy

Variant Pricing Strategy was introduced to the Product domain to assist users who price Variants based on the Product’s SKU rather than the Variants' own. By default, Broadleaf sends PriceableTargets to the Pricing Service for every Variant as well as the parent Product. In this case, the Product’s pricing (whether by SKU or pricing key) acts as the fallback if a Variant doesn’t have a price specified.

However, there are cases where users never specify variant-specific pricing, using other mechanisms to vary pricing such as the new Two-Tier pricing targeting Characteristic values. Consequently, performance can be improved by skipping sending the PriceableTargets for the Variants and instead only using the Product’s price. This is additionally useful in such cases when there are also a very large number of variants.

Therefore, a new field has been added to Product, variantPricingStrategy, to determine whether to price by the product only.

The supported values out-of-box:
  • PRODUCT: All the variants will always use product-level pricing, e.g., catalog’s product-level pricing, price data targeting pricing key, etc., useful to if variant-level pricing isn’t needed.

  • VARIANT: Variant-level pricing will take precedence, e.g., price data targeting variant SKU or pricing key. If variant-level pricing isn’t available, product-level pricing will still be used.

    • This is the previous behavior.

  • DEFAULT: Delegates to the system setting to determine whether Product or Variant pricing strategy should be used.

    • Set the system behavior with broadleaf.common.default-configurations.product.variant-pricing-strategy.

Other Enhancements

  • Characteristics admin form improvements

    • When Characteristic is of Date type, the translatable option is not shown

    • When Characteristic is of Variant Options type, the cardinality option is not shown

    • Tooltip was added for Min Cardinality field

  • Required validation is now enforced for product characteristics with minimum cardinality greater than 0 on a storefront.

  • BusinessTypeEndpoint readAll operations now support pagination

    • Introduced new GET /all-paginated operation. Full url /product-types/all-paginated. Will return a paginated list of BusinessType instances, including support for out-of-box default types and template types.

    • Introduced new GET /component-mappings-paginated operation. Full url /product-types/component-mappings-paginated. Will allow callers to request a page of component mappings returned as a map structure keyed by business type key

    • Deprecate GET /all, use /all-paginated instead

    • Deprecate GET /component-mappings, use /component-mappings-paginated instead

    • The admin metadata for Business Types has been updated to leverage the new paginated endpoint.

      • If you have customized or overridden this metadata, you may want to consider updating the logic to match the following:

        .addPrimaryAction(Actions.action()
                .label("product.browse.actions.create")
                .type("CREATE_CHOICE")
                .addEndpoint(Endpoints.pageableGet()
                        .narrowedPaging()
                        .type("EXTERNAL_CHOICES")
                        .uri("/catalog/product-types/all-paginated")
                        .param("q", "${filter.q}")
                        .param("cq", "${filter.cq}")
                        .param("includeDefaults", "true")
                        .param("includeTemplates", "false")
                        .scope(BusinessTypeScopes.BUSINESS_TYPE))
                .scope(BusinessTypeScopes.BUSINESS_TYPE)
                .attribute("createPathTemplate", "/products/${typeKey}/create")
                .scope(ProductScopes.PRODUCT))
  • Product options derived from characteristics are now filtered to only include active characteristic values before being sent to the frontend.

  • Added color and showInAdmin properties to AdvancedTag domain model.

    • color allows specifying a color for the tag, which is used in admin UI displays.

    • showInAdmin determines if the tag should be visible in admin interfaces.

  • Form field and group ordering improvements in admin characteristic forms.

  • Implemented Business Type Characteristics Inheritance

    • If a Business Type’s productType key is not any of the DefaultProductType enums (meaning it is not one of the default product types that are included out-of-box), then it is pointing to another Business Type whose typeKey matches the productType key. This indicates that this Business Type can inherit certain attributes from its parent Business Type (also known as its ancestor), such as Characteristics.

    • When updating a Product whose Business Type has a parent Business Type, the Product is hydrated with all inherited characteristics from its ancestor Business Types. See BusinessTypeAncestryHydrationService to see how this hydration is performed.

    • The Business Type Characteristics grid displays each Characteristic’s Inheritance Type which determines how the Characteristic was assigned to the Business Type:

      • OVERRIDDEN - Characteristics that were initially INHERITED but were edited by a user. These Characteristics are persisted as part of the Business Type’s characteristics field.

      • CUSTOM - Characteristics that are independently created by users. These Characteristics are persisted as part of the Business Type’s characteristics field.

      • INHERITED - Characteristics that are inherited from ancestor Business Types. These Characteristics are not persisted as part of the Business Type, but instead are hydrated based on the Business Type ancestors field.

  • Since DefaultProductService now manages both Product and ProductCharacteristic persistence within its CRUD methods, changes had to be made to ensure SingleIndexRequest is only emitted for the Product after all the persistence for ProductCharacteristic elements is complete. This ensures the product will never be reindexed in an incomplete state - it will only be reindexed once both the Product and all of its ProductCharacteristic instances have finished persisting. Several new components are involved in achieving this result:

    • SingleIndexRequestNotificationStateBuildContext is leveraged to ensure the notification state for SingleIndexRequest is still built, even when SuppressNotificationContext is used to suppress the eager initial message send attempt.

    • ProductPersistedDomainCollectingMapperMember and CollectedProductPersistedDomainContext are used to allow DefaultProductService to have access to the persisted-domain instances, which it passes to NotificationManager to invoke the eager SingleIndexMessage send after persistence finishes.

  • Replaced ModelMapper-based cloning logic with a new Kryo-based ObjectDeepCopyUtility for significantly improved cloning performance during product hydration and reindexing

    • Introduced a new Kryo-based ObjectDeepCopyUtility bean which can 'deep clone' any input object

    • Updated CategoryProductConsolidationContributor, ContextualProductConsolidationContributor, and DataDrivenEnumConsolidationContributor to use this new utility for cloning, replacing the previous ModelMapper 'clone mapper' approach

  • Some flows (notably the ContextualProductConsolidationContributor components) were constructing RSQL Node instances by first building a filter string and then parsing it. Many of these flows now manually construct the Node filters directly, improving performance.

  • Updated the out-of-box metadata for the Price Data Form in the Product View to allow explicitly defining a pricing key for Standard, Variant-Based, and Bundle products.

    • Updated the pricingKey field conditionals in the price data form to be visible for these product types.

    • Renamed the "SKU" column to "SKU or Pricing Key" and updated the Price Data grid to also render price data that is targeting the product via a pricing key.

    • Added new /products/{productId}/target-ids endpoint to retrieve all target IDs (SKUs and Pricing Keys) associated with a product. This is used to render them as filter options in the Price Data grid.

  • Updated the "Allowed Values" section in the Characteristic and Product Option forms to only be visible when the value type is set to Variant Option, Enumeration, or Boolean. This ensures a cleaner user interface by hiding irrelevant configuration fields for simple data types (e.g., String, Integer, Date).

    • When a Boolean Characteristic that is used in a Product Type is set to be configurable, the allowed values section that is displayed in the Product Options for a Product only displays "True" and "False" as options, since those are the only two valid options for a Boolean Characteristic. The labels for these options are updatable and can also have translations provided for them, but the actual values are fixed to be "True" and "False" in order to prevent any invalid values from being entered for Boolean Characteristics.

  • Removed product characteristics from the general "Characteristics" section of the Product form that are marked as configurable, since those will appear in the "Add-Ons and Options" section.

  • Updated Product Choice product selection metadata filters to target the Product.productType field instead of Product.businessType field when fetching Products, and updated the relevant Product endpoint to support filtering by Product Type as well.

  • Enhanced the AddOn product pricing management for the SpecificItemChoice in the admin view

Enhancement: Added support to show/hide recurring pricing fields by properties

Instead of always showing the recurring pricing metadata fields (e.g. terms and frequency), the metadata is now controlled by a different property:

broadleaf:
  catalog:
    metadata:
      display-recurring-pricing-fields: true
Note
Enabling subscription via broadleaf.subscription.enabled implicitly enables the recurring pricing fields too

Bug Fixes

  • Fix a bug where products would go missing from search results if a product inherited from a tenant context had a related entity (ex: product tag) defined in an application context

    • DefaultConsolidatedProductPostProcessor was failing to calculate the right creatingApplicationId for synthetically generated ConsolidatedProduct instances during the indexing flow. The new implementation leverages the new ContextState.creatingApplicationId field made available in DataTracking 2.0.7 to accurately populate this value. All the consolidation contributors have also been updated to populate the new creatingApplicationId and overrideCatalogLevel fields on ContextState to ensure consistency.

  • Fixed an issue where immutable products from a child catalog are not disabled when reordering category products.

  • Added validation to ensure that active OptionTemplate entities that are directly referenced cannot be deleted.

  • Cart Item Attribute Product Option fixes

    • Fixed an issue in the admin form where the required toggle could get stuck in the "not required" position, even after setting the attribute as required.

    • Fixed an issue where integer values could be incorrectly flagged as invalid

    • Fixed an issue where the add to cart button could be disabled even after all required cart attributes are satisfied

  • Fixed untranslatable characteristics like booleans being included in translation requests

    • Instead of using a conditional on the translatable field, we use a dynamic field where the translatable flag is hidden and defaulted to false for untranslatable Characteristic types like Boolean.

  • Fixed that a characteristic created from the business type form will be placed into the basic information field group instead of the characteristic group

    • For that purpose the new metadata attribute defaultSelectedComponentId for TargetKeyLookup component was introduced

  • Fixed the issue when after product import of a business type with enum characteristics, the product further editing returns an error

  • Improved hydration logic so configurable characteristics aren’t unnecessarily recreated, preventing false overrides and propagation issues. “By Reference” option templates also benefit from this fix.

  • Updated characteristics metadata so that the characteristic’s allowed values are read-only when derived from enumeration type characteristics.

  • Fixed NPE when trying to generate SKUs from variant option characteristics when no characteristic actually exists.

  • Fixed issue related to modifying a collection in a stream causing duplicated product options to be returned during the product hydration process.

  • Fix a bug in ContextualProductConsolidationContributor where ContextState was not being properly deep copied during product override generation, and therefore any subsequent mutation to that instance would affect all cloned instances.

  • Fixed an issue where minimumQuantity of the Selector product option was updated even when unchanged.

  • Fixed an issue where overlapping message.properties files would cause incorrect message/label resolution for services that have their own message.properties file in addition to the main one in Catalog.

  • Fixed an issue where DefaultCloneProductService would clone with translated data for the caller’s locale instead of the raw values. The flow has been updated to leverage ContextInfo.isIgnoreTranslation to ensure only raw values are cloned. This primarily affected entities related to Product (such as Variant) rather than Products themselves, but the issue is now fixed for all entities in the flow.

  • Fixed a bug in the Category Product grid where attempts to sort products inherited from a parent catalog would fail. The grid metadata was updated to include a selectableWhenMutableOnly attribute that makes the sort action inaccessible to immutable products.

  • Fixed a bug where JpaCatalogQueryHelper#countNonProductionRecordsByProductIdsInTenant did not exclude sandbox-archived records in its query criteria, potentially resulting in false-positive validation errors during entity deletion

  • Fixed a bug where the CatalogCurrencyContextInfoCustomizer could throw an IllegalStateException when not in a request context. A check was added to prevent interacting with the CurrencyHolder outside of a request context.

Data Migration

Populating Default Frequency for Products with Recurring Pricing

For existing products with recurring pricing, it’s recommended to populate the default frequency, so that the PDP and PLP can show the initial pricing as expected.

Most of the time, you can just populate MONTHLY frequency for the product with recurring pricing with the following SQL:

UPDATE blc_product SET default_recurring_frequency = '{"recurringPeriodFrequency":1,"recurringPeriodType":"MONTHLY"}' WHERE context_id IN ('some-ids', 'some-ids-2');

Alternatively, you can use terms to determine what records to update instead of context_ids:

UPDATE blc_product SET default_recurring_frequency = '{"recurringPeriodFrequency":1,"recurringPeriodType":"MONTHLY"}' WHERE terms IS NOT NULL AND terms != '[]';

Note: For this option, you will still need to update the default recurring frequency using the ids for recurring products with no terms.

Improved support for AddOn pricing and the UX for its configuration

Important
Requires Catalog Services 2.3.0
  • Updated the pricing comparator to prioritize as follows:

    • VariantPricingOverride if it exists

    • PriceData targeting the add-on’s pricing key

    • Direct price set on the add-on

    • Underlying entity match (follows existing logic for priority (e.g. price data, price lists, direct pricing)

  • Removed logic that always builds priceableTarget for defaultProduct and defaultVariant when building for options

    • This target is redundant because we are already building the targets for all the specific choices, which the default product or variant is already in there too

  • Added logic to build the PriceableTargets for underlying product’s variants for specific item choices

  • Updated the ProductOptionsProductPriceableTargetsBuilder to return empty set of targets early if override price exist for the ItemChoice or SpecificItemChoice

    • No need to build and price the underlying products/variants if the ItemChoice or SpecificItemChoice already has an explicit price override

  • Added support for variant based products to be a product’s add-on

Inventory Availability Index

Introduced a new GET /inventory-availability-index/{productId} endpoint that returns basic variant information along with inventory availability information for a given product. This endpoint is intended to be used in commerce flows to retrieve inventory availability information for all variants of a product in a single request and replace hydrating full VariantDetails onto the ProductDetails. The index uses the variant option values as the keys to allow fast lookup in the frontend to be used alongside option selector components to hide/grey-out unavailable options and the add-to-cart button.

Example Payload
type InventoryAvailabilityIndex = {
  variants: {
    "gold:XL": {
      id: string;
      sku: string;
      optionValues: Record<string, string>;
      available: boolean;
    }
  }
}

Primary Use Case

Using this endpoint rather than hydrating the full variant details is particularly useful for products with large numbers (e.g., hundreds) of variants to improve performance and time to first paint. To maximize its effect, set default variant hydration behavior for product details in Catalog Service to NONE and have the frontend make a separate call to fetch the inventory availability index in asynchronously such as in a useEffect or server function. This allows the main content to load and be painted more quickly while the availability index is retrieved before the user can actually interact with the page.

Disable Variant Hydration for Product Details
broadleaf:
  catalog:
    product:
      details:
        variant-hydration-behavior: NONE

Other Implementation Details

  • Added new InventoryAvailabilityIndexService to orchestrate calls to the Catalog and Inventory providers to fetch lite variant and inventory availability information for all variants of a product.

  • Added a new LiteVariant DTO, which is a lightweight representation of a Variant.

  • Added new fetchLiteVariantsForProduct method to the ExternalCatalogProvider

  • Added new getInventoryAvailabilityForSkus method to the ExternalInventoryProvider

    • The default implementation builds a collection of SkuInventoryAvailabilityRequests and calls the bulk availability API in Inventory Services.

  • The map key for AvailabilityInfo#variants takes the variant option values and joins each value with a semicolon in alphabetical order by key. For example, given the following option values

    {
      "size": "XL",
      "color": "gold"
    }

    The key would be gold:XL. ** To modify this behavior, extend DefaultInventoryAvailabilityIndexService#buildOptionValuesKey

Bug Fixes

  • ExternalInventoryProvider.getInventorySummaries was previously making a request to inventory services even when there were no items to provide in the request payload (for example, all requested products had inventory check/reserve strategy settings set to NEVER). There is now a check in this flow to examine whether the requested item list is empty before proceeding to make a request to inventory services, reducing unnecessary system load.

  • Fixed an issue where the "Pay In Full" button appears twice for Products configured with recurring pricing and a default frequency that is not "One-Time" on the Product Details Page.

  • Fixed an issue where incorrect and redundant Purchasing Options are being displayed for Variant Products whose Base Price is different from the variant’s price.