Please refer to 2.1.5 Catalog Release Notes and 2.1.5 Search Release Notes for more information.
Multiple changes have been made to Solr documents in Search Services and Consolidated Product in Catalog Services in order to make Solr Search improvement and fixes on narrowing capabilities.
Both of these services must be updated in tandem for the changes to work.
These have been documented at the respective service level release notes: 2.1.5 Catalog Release Notes and 2.1.5 Search Release Notes.
Introduced a new approach to retrieve audit details for campaign codes
The campaign_id
column was added to BLC_SHARED_CODE_AUDIT_SUMMARY
table, and now it is the key column to query data for campaigns. A migration is required for historical data to be shown 3.0.6 Offer Release Notes
Feature/Notable Change | Impacted Services | Links |
---|---|---|
Solr Search Narrowing Improvements and Fixes |
Catalog Services, Search Services |
Please refer to 2.1.5 Catalog Release Notes and 2.1.5 Search Release Notes for more information. |
Campaign Offer code audit |
Offer Services |
Please refer to 3.0.6 Offer Release Notes for more information. |
Stripe updated to use stripe elements instead of deprecated Stripe Express Checkout. |
Stripe |
|
Due to the Campaign Offer Code audit tab enhancements, the database migration is required in order to display legacy code uses |
Offer |
|
Open Telemetry support |
Common Libraries, Base Dependencies, Sandbox Services |
ContentServices microservice has security-related updates.
MicroservicesGateways have security-related updates.
To review the security related content, see 2.1.5 notes.
Tip
|
You will need your login credentials originally provided for accessing the Broadleaf nexus. Security fixes often involve dependency updates to remediate issues being tracked in external OSS components. It is worth considering adopting releases with security fixes (even Broadleaf Severity LOW) to avoid any possibility of transitive exposure in your codebase. |
This was a simple change to the node engines in the package.json to accept Node 20 after testing.
Important
|
This relies on changes made to Admin Metadata Services 2.1.4 and Catalog Services 2.1.5. |
Tip
|
To revert to the prior behavior, set VITE_METADATA_ROUTER_ALLOWMULTIPLEROUTESPERPATH=false .
|
This allows multiple routes to be mapped to the same path, e.g., /products
, and be distinguished by some other property like scope or component ID.
It will render the first match that is authorized to be seen rather than just the first match.
Added new multi-route rendering component, ComponentRoutes
.
This component will use a new endpoint in AdminServices to read all of the metadata by component ID for the Routes matching a given path, e.g., /product
. This endpoint can be changed using VITE_METADATA_COMPONENTS_URL
, which is /metadata/component
by default.
It will check the permissions of each Route by their scopes and render the first one that is authorized and has metadata returned from the backend.
Overridable via blComponentRoutes
key.
Internally uses new ComponentRouteRenderer
to render the authorized and resolved metadata for the Route.
Overridable via blComponentRouteRenderer
key.
Made pre-existing single route rendering components overridable:
ComponentRoute
as blComponentRoute
This wraps ComponentRouteRenderer
in a react-route Route
With the above changes, this will only be used if there is only one Route mapped to a path.
ComponentRouteRenderer
as blComponentRouteRenderer
This handles resolving metadata for a Route and checking user access to it
Added support restricting routes by matching all scopes not just any
Using new scopeMatchType
property on Route
, taking values ANY
and ALL
. Default is ANY
.
Support configuring a label for the customizable views of a business type using the _adminLabel
attribute on the augmented metadata
Added better semantic HTML tags for certain elements
To revert to using divs, set VITE_MAIN_LAYOUT_VERSION=v1
Marking the main
content of the app in BaseLayout
Wrapping the main Navigation in nav
Using lists and list elements where appropriate
Using buttons on clickable components that are not links
Using section, header, and footer to better describe the organization of components
Added component name style classnames to various components of the Navigation and Header
Moved the tailwind utility classes to external CSS to take advantage of the cascading nature of CSS for easier overriding
Made the subcomponents of these overridable
Enhanced the existing logic for setting the nameLabel
for custom attributes to place it at the FieldTypeRenderer
level.
Added an additional status message to the SandboxRibbon
that displays the total amount of changed items in a user’s sandbox across all entities.
The text also serves as a link that redirects to the My Changes page for the user.
Enhanced the Admin navigation menu sections to be able to be toggled open and closed by clicking on the menu section title.
Added support to AssetBrowserView
for a default sort to be provided via metadata
Make the progress bar in FulfillmentViewDetails
and the FulfillmentCardBody
customizable via metadata.
A custom mapping of Fulfillment Statuses to Percentages can be provided via the fulfillmentStatusToPercentageMap
metadata attribute. Otherwise, the default mapping based on the out-of-box fulfillment statuses will be used.
Enhanced EntityLongView
component to include the parent ID of a nested component in the Formik initial values as part of the attributes.
Added new attributes to EntityGridView
to support restricting visibility of grids to tenant-only, application-only, or specific application contexts.
tenantOnly
: Boolean flag.
applicationOnly
: Boolean flag.
restrictionApplications
: Array of strings that are the IDs of the applications the grid is restricted to.
Fixed losing Application and Catalog context after cache timeout by using stale-while-revalidate caching strategy
Added validation for required fields in Add New Field Augmentation form
Fixed Workflow Orchestration Map in Workflow Details view
Fixed precision of matching Admin Navigation menu items based on the current route so that routes with similar names are not matched.
For example, /workflow
should not match /workflows
Fixed bug where the Add New Field modal for modifying Business Type Create/Update forms could be submitted without required fields having any values — applied validation methods to all required fields.
Fixed bug where the Modify Update Form button does not display if the Business Type name contains the word create
.
Fixed business type fields being clickable when form is disabled/read-only
Fixed instances where we were still using tw-shadow-outline
instead of tw-ring
Added missing variant for motion-reduce
Fixed cases where we used transition
or transform
instead of tw-transition
and tw-transform
Fixed some cases where we used group
instead of tw-group
Added missing localizable message property for the User Settings dropdown in the header
Header.user-settings.label
Resolved an issue where Customer Segment Rules containing two or more rules with special characters (e.g., @, ., etc.) in their values were incorrectly displayed as a single concatenated string after saving and reviewing.
Fixed the behavior when selecting a new folder so that the Asset item filter and sort params are not cleared. Instead, these will be retained when switching folders.
Fixed an issue where the ApplicationSelector
would fail to list all the applications if the number of applications exceeded the page size.
Introduced new TenantService#getApplicationsWithQuery
method, which supports pagination with querying.
Modified the ApplicationSelector
to fetch applications directly using the new method, instead of relying on the TenantContext
.
Fix Default Application implementation to account for users with Tenant access but also have restricted applications & a default application ID assigned to them
Fixed issue where clicking only the text link of the My Profile
button would redirect to the /my-preferences
page — redirecting is now done at the dropdown item level instead of a child Link
component
Fixed issue where ApplicationSelector
was not allowing users to exit out of the default application
On mobile devices: Fixed the icon used to expand or collapse the navigation menu in the header to be the same height as the header. Previously it was slightly overhanding the header.
Fixed the sandbox ribbon messaging sometimes causing the main content to be pushed underneath the sidebar when the navigation menu was open.
The text will not be truncated rather than forcefully extending the ribbon’s width.
This usually only happened on tablet sized screens when many changes had been made to an entity in a sandbox.
Fixed Rule (SpEL) and Query (RSQL) builders not displaying values on initial modal render
The form state was not initialized immediately when the modal was opened, causing the Rule and Query builders to show blank values. To fix this, the modal now waits to render until the form state is initialized.
Add new otherMessagesMap
and otherItemMessagesMap
map fields to the MarketingMessageResponse
.
Introduce invalidatePricing
flag:
Add invalidatePricing
field to the PriceableCartClientCallOptions
type, and to the AddAttributeRequest
interface.
Add attachInvalidatePricingParamIfPresent
method to the CartClient
.
Add new X_Customer_Ref
header to cart requests:
This header is useful for CSRs acting as themselves rather than impersonating a customer to retain the cart’s customer ref pricing when modifying a cart.
Add customerRef
to CheckoutClientCallOptions
and CartClientCallOptions
.
Add attachCustomerRefHeaderIfPresent
to the CartClient
and the CheckoutClient
.
Add a useAddAttributeToCart
hook to facilitate requests to cartClient#addAttributeToCart
.
Add a useRemoveAttributeFromCart
hook to facilitate requests to cartClient#removeAttributeFromCart
.
The Passthrough Payment Form now uses the useAddAttributeToCart
hook to update the PAYMENTS_LIST
when a new payment is submitted or removed.
The AddAttributeRequest
contains invalidatePricing: true
, so that the cart will be repriced once the attribute has been updated.
The PAYMENTS_LIST
attribute is used by the backend to apply offers based on the type of payment. For more details visit the Cart Operation Service release notes.
Modified all usages of Customer Access Tokens to pass in limited scopes when fetching the access tokens.
A new configuration property named NEXT_PUBLIC_PAYMENT_LIMIT_CUSTOMER_TOKEN_SCOPES
was added to limit the Customer Access Tokens scopes that are used in Payment Gateways requests. See the Commerce NextJS Starter Configuration Guide for more.
Fix bug where Quote pricing was being overridden by a CSR after making changes, removing any customer-segment based offers that apply to the quote owner.
Include the X-Customer-Ref
header in any requests to modify a cart when isCsrSelf(user)
is true
.
Fixed bug where the type ahead dropdown did not open if there were product suggestions but no category suggestions.
Added menu item for the new Content Field entity.
Added menu item for the new Search Synonym Management Section.
Introduced a separate ALL_ADMIN_USER_PROFILE
permission and ADMIN_USER_PROFILE
security scope.
Before this, the general ADMIN_USER
scope was used to gate whether an admin user can modify their own preferences, but that scope also provides access to view and manage other admin users.
The ALL_ADMIN_USER_PROFILE
permission has also been added to all roles out of box for users of the Broadleaf Initializr.
The previous ADMIN_USER
scope will also still function as before to access the user preference endpoint(s), but the new permission allows finer access control.
INSERT INTO auth.blc_security_scope (id, name, open) VALUES ('-10009', 'ADMIN_USER_PROFILE', 'N');
INSERT INTO auth.blc_permission_scope (id, permission, is_permission_root, scope_id) VALUES ('-10009', 'ADMIN_USER_PROFILE', 'Y', '-10009');
INSERT INTO auth.blc_user_permission (id, archived, last_updated, name, is_account_perm, user_assignable) VALUES ('-10011', 'N', '2025-05-21 12:53:58.413970', 'ALL_ADMIN_USER_PROFILE', 'N', 'Y');
INSERT INTO auth.blc_user_permission (id, archived, last_updated, name, is_account_perm, user_assignable) VALUES ('-10012', 'N', '2025-05-21 12:53:58.413970', 'READ_ADMIN_USER_PROFILE', 'N', 'Y');
-- map to existing admin roles --
-- PARTIAL_ACCESS
INSERT INTO auth.blc_role_permission_xref (role_id, permission_id) VALUES ('-1', '-10012');
-- FULL_ACCESS
INSERT INTO auth.blc_role_permission_xref (role_id, permission_id) VALUES ('-2', '-10011');
-- APPLICATION_ACCESS
INSERT INTO auth.blc_role_permission_xref (role_id, permission_id) VALUES ('-7', '-10011');
-- ROLE_MARKETPLACE_OPERATOR
INSERT INTO auth.blc_role_permission_xref (role_id, permission_id) VALUES ('-200', '-10011');
-- ROLE_VENDOR_ADMIN
INSERT INTO auth.blc_role_permission_xref (role_id, permission_id) VALUES ('-201', '-10011');
-- ROLE_VENDOR_MERCHANDISER
INSERT INTO auth.blc_role_permission_xref (role_id, permission_id) VALUES ('-202', '-10011');
-- openapi
INSERT INTO auth.blc_client_scopes (id, scope) VALUES ('openapi', 'ADMIN_USER_PROFILE');
INSERT INTO auth.blc_client_permissions (id, permission) VALUES ('openapi', 'ALL_ADMIN_USER_PROFILE');
This changes the way in which the admin user preferences endpoints resolved the currently authenticated admin user.
Instead of using the name on the principal (the username), the system now uses the admin_user_id
claim.
This is better since the username might not match the case stored in the database and the context ID is more reliably and efficiently indexed for lookups since it is a ULID.
Important
|
We also deprecated AdminUserService#updateUserPreferencesByName in favor of #updateUserPreferences .
|
Tip
|
The 2.x versions are Spring Boot 3 compatible. |
This version includes all changes up to 2.1.5 Release Notes.
The previous default behavior when matching a Route’s security scopes was to match any.
Route
now has a scopeMatchType
property that can be set to either ANY
or ALL
, with ANY
as the default.
Related changes have been made to Metadata Common 2.0.7.
@Bean
public ComponentRouteLocator productMetadataRoutes(RoutesBuilder routesBuilder) {
RoutesSpec routes = routesBuilder.routes()
.route("/products", r -> r.componentId("catalog:porducts:browse")
.scope("PRODUCT")
.scope("CUSTOM_ADMIN_VIEW_SCOPE")
.allMatchScopes(); // <-- NEW, default is `.anyMatchScopes()`
return routes.build();
}
GET /api/metadata/component
taking a comma-separated list of component IDs as the ids
query parameter.
This is in support of allowing multiple Routes to be mapped to the same Route and differentiated by security scope. Security scopes are evaluated in the Admin App rather than on the backend when determining whether to render a route.
Configuration properties were add to determine what routes and route components should be generated when a Business Type is created. Previously, this was hardcoded to assume single base versions of the product browse, create, and update views. With these changes, users can now configure an unlimited number of routes including all the granular details like path, parent component key, and required scopes. The currently hardcoded values are now the default values for the new properties.
Note
|
Also added was a property to configure the supported entity types for the MetadataProductBusinessTypeModifiedHandler instead of just hardcoding PRODUCT , using broadleaf.metadata.business-type.product.supported-entity-types .
|
The following property takes a list of RouteProperties POJOs: broadleaf.metadata.business-type.product.routes
.
broadleaf:
metadata:
business-type:
product:
routes:
- pathTemplate: /products/{businessType}
routeComponentType: BROWSE
# routeComponentLabel: <optional>
componentKeyTemplate: catalog:products:{businessType}
parentComponentKey: catalog:products:browse,
scopes:
- PRODUCT
# optional, default is ANY
scopeMatchType: ANY
- pathTemplate: /products/{businessType}/create
routeComponentType: CREATE
# routeComponentLabel: <optional>
componentKeyTemplate: catalog:products:{businessType}
parentComponentKey: catalog:products:{parentType}:create
scopes:
- PRODUCT
# optional
scopeMatchType: ANY
- pathTemplate: /products/{businessType}/:id
routeComponentType: UPDATE
# routeComponentLabel: <optional>
componentKeyTemplate: catalog:products:{businessType}
parentComponentKey: catalog:products:{parentType}:update
scopes:
- PRODUCT
# optional
scopeMatchType: ANY
It may be the case that users want their Admins to see different versions of a view based on their role, e.g., a Merchandiser’s vs a Super Admin’s view of Products. If these views are sufficiently different from each other, it may be easier to configure separate view components rather than adding conditionals to each group and field of the Super Admin’s view. Given that these views are distinct, they also must have different component IDs, and, therefore, different routes configured. However, the routes will have the same path and will only be differentiated by the required scopes to access them.
Take the following as an example where we set up different routes for the Merchandiser and Super Admin views of Products.
@RequiredArgsConstructor
public class CustomProductMetadataRoutesAutoConfiguration {
private final CatalogMetadataProperties properties;
@Bean
public ComponentRouteLocator productMetadataRoutes(RoutesBuilder routesBuilder) {
RoutesSpec routes = routesBuilder.routes();
addRoutes("catalog:products:%s", "SUPER_ADMIN_VIEW");
addRoutes("merchandiser:catalog:products:%s", "MERCHANDISER_ADMIN_VIEW");
return routes.build();
}
private void addRoutes(String componentId, String scope) {
routes.route("/products", r -> r.componentId(componentId.formatted("browse"))
.scope(scope)
.scope(ProductScopes.PRODUCT)
.allMatchScopes());
getAvailableProductTypes().forEach(type -> {
routes.route("/products/%s/create".formatted(type.name()),
r -> r.componentId(componentId.formatted(type.name() + ":create"))
.scope(scope)
.scope(ProductScopes.PRODUCT)
.allMatchScopes());
routes.route("/products/%s/:id".formatted(type.name()),
r -> r.componentId(componentId.formatted(type.name() + ":update"))
.scope(scope)
.scope(ProductScopes.PRODUCT)
.allMatchScopes());
});
}
private List<DefaultProductType> getAvailableProductTypes() {
return Arrays.stream(values())
.filter(t -> properties.getActiveProductTypes().contains(t.name()))
.collect(Collectors.toList());
}
}
With this, there are now two different views for each view type for Products.
Therefore, when a new Product Type (a.k.a., Business Type) is created, it needs to generate two different routes for each of the three view types rather than just one.
Moreover, the routes need to be distinguished by their scopes so that the correct one is rendered based on the user’s role.
broadleaf.metadata.business-type.product.routes
will now allow for this.
broadleaf:
metadata:
business-type:
product:
routes:
# First, override the default routes for the Super Admin
- pathTemplate: /products/{businessType}
routeComponentType: BROWSE
routeComponentLabel: Super Admin
componentKeyTemplate: catalog:products:{businessType}
# We point to the base product views as the parents for the super admin
parentComponentKey: catalog:products:browse
# we add our custom scope to gate this version of the view
scopes:
- SUPER_ADMIN_VIEW
- PRODUCT
# This will require both our new scope and the default PRODUCT scope to be present
scopeMatchType: ALL
- pathTemplate: /products/{businessType}/create
routeComponentType: CREATE
# This will result in the Product Type form having actions for customizing the views
# having a label of "Modify Super Admin Create View" and "Modify Super Admin Update View".
# This will distinguish one view type from the other.
# By default the labels are simply "Modify Create View" and "Modify Update View".
routeComponentLabel: Super Admin
componentKeyTemplate: catalog:products:{businessType}
parentComponentKey: catalog:products:{parentType}:create
scopes:
- SUPER_ADMIN_VIEW
- PRODUCT
scopeMatchType: ALL
- pathTemplate: /products/{businessType}/:id
routeComponentType: UPDATE
routeComponentLabel: Super Admin
componentKeyTemplate: catalog:products:{businessType}
parentComponentKey: catalog:products:{parentType}:update
scopes:
- SUPER_ADMIN_VIEW
- PRODUCT
scopeMatchType: ALL
# Second, configure new routes for the Merchandiser admin
- pathTemplate: /products/{businessType}
routeComponentType: BROWSE
routeComponentLabel: Merchandiser
componentKeyTemplate: merchandiser:catalog:products:{businessType}
# here is where we pick the base view to use that we configured in java above
parentComponentKey: merchandiser:catalog:products:browse
# we user the merchandiser's scope instead of the super admin's
scopes:
- MERCHANDISER_ADMIN_VIEW
- PRODUCT
scopeMatchType: ALL
- pathTemplate: /products/{businessType}/create
routeComponentType: CREATE
# This will result in the Product Type form having actions for customizing the views
# having a label of "Modify Merchandiser Create View" and "Modify Merchandiser Update View".
routeComponentLabel: Merchandiser
componentKeyTemplate: merchandiser:catalog:products:{businessType}
parentComponentKey: merchandiser:catalog:products:{parentType}:create
scopes:
- MERCHANDISER_ADMIN_VIEW
- PRODUCT
scopeMatchType: ALL
- pathTemplate: /products/{businessType}/:id
routeComponentType: UPDATE
routeComponentLabel: Merchandiser
componentKeyTemplate: merchandiser:catalog:products:{businessType}
parentComponentKey: merchandiser:catalog:products:{parentType}:update
scopes:
- MERCHANDISER_ADMIN_VIEW
- PRODUCT
scopeMatchType: ALL
Important
|
This depends on changes in Metadata Common 2.0.7-GA. |
Added support for specifying a default sort on a field to the AssetBrowserView
similar to other grids.
Default sort is set to title for Assets.
Note: This component does not support users dynamically sorting columns in the admin, just applying a default sort.
Tip
|
The 2.x versions are Spring Boot 3 compatible. |
New permissions and scopes have been added and should be inserted into the auth database with the following SQL:
INSERT INTO blc_security_scope ("id", "name", "open")
VALUES ('-3000', 'CART_INTERNAL_ATTRIBUTES', 'N');
INSERT INTO blc_permission_scope ("id", "permission", "is_permission_root", "scope_id")
VALUES ('-3000', 'CART_INTERNAL_ATTRIBUTES', 'Y', '-3000');
INSERT INTO blc_user_permission ("id", "archived", "last_updated", "name", "is_account_perm", "user_assignable") VALUES ('-3000', 'N', '1970-01-01 00:00:00.000', 'ALL_CART_INTERNAL_ATTRIBUTES', 'N', 'Y');
If you’re consuming the openapi
client for use with OpenAPI, the following permission updates are necessary:
INSERT INTO blc_client_scopes ("id", "scope") VALUES ('openapi', 'CART_INTERNAL_ATTRIBUTES');
INSERT INTO blc_client_permissions (id, "permission") VALUES ('openapi', 'ALL_CART_INTERNAL_ATTRIBUTE');
Tip
|
The 2.x versions are Spring Boot 3 compatible. |
Support permission-based mutation of Cart Attributes & Cart Internal Attributes:
Add cartInternalAttributes
and cartAttributes
to CartCreationRequest
Add validation to any endpoints in ManageCartEndpoint
that may include attributes or internal attributes in the request.
See Authentication Services Release Notes for the new permissions and scopes needed to mutate these attributes.
Fix the issue for proper handling of COD payment added to the cart when a stale cart item is removed
Fixed issue where a duplicate key error would be thrown when adding an item to a cart multiple Fulfillment Groups with the same Fulfillment Type.
Updated a priceCart
call in DefaultFulfillmentOptionRequestService
to utilize the Cart Operations Service instead of the Cart Pricing Service.
This fixes an issue where pricing was not correctly hitting important flows such as gift calculations.
Fixed an issue where gifts could not be removed from the cart if the gift offer had no qualifiers attached to it.
This keeps the gift in a state where it continues to have an entry in the internal attributes for OFFER_IDS_TO_IGNORE_MAP
, similar to how it works when there are qualifiers attached.
Fixed an issue where a merging-type
of COMBINE
for CartItems
would not correctly keep gifts separate when a similar item to the gift is added to the cart.
Similar items added to the cart are still combined, but gifts are separated unless another gift of the same type is added.
Tip
|
The 2.x versions are Spring Boot 3 compatible. |
JDK 17 is required for Broadleaf release trains 2.0.0-GA, and beyond.
This version now requires DataTracking 2.0.3+
This version is required by SearchService 2.1.5+ documented in the 2.1.5 Search Release Notes
Important
|
Also see changes made in Admin Metadata Services 2.1.4 |
Instead of being hardcoded as part of the BusinessType metadata, the BusinessTypeFields
metadataMappings
are now derived from a property: broadleaf.catalog.metadata.product-business-type-route-component-keys
.
broadleaf:
catalog:
metadata:
product-business-type-route-component-keys:
- catalog:products:${typeKey}:create
- catalog:products:${typeKey}:update
This is to make indexing the necessary information about merchandising product items easier by consolidating the key information about the required add-on ProductOptions into a single structure. More details in Catalog Search Integrations, particularly ConsolidatedProduct#requiredOptionInfo and RequiredOptionInfoProductConsolidationContributor.
This uses a new ProductConsolidationContributor
, RequiredOptionInfoProductConsolidationContributor
, that replaces ItemChoiceAvailabilityConsolidationContributor
.
It does the same availability checks for IncludedProducts and ItemChoice ProductOptions as well consolidate the rest of the information for required ProductOptions that might relate to pricing.
This new class was used due to the number of changes required to adequately and efficiently hydrate the related Products and Variants onto the ItemChoices.
Tip
|
To turn off the new contributor and re-enable the old contributor, users can set
|
Note
|
Also changed ProductDetails hydration so that the ItemChoice#defaultProductInCategory is always hydrated even if the request specifies to not hydrate the category products to allow pricing to work correctly for merchandising products.
The non-default products will continue to not be hydrated unless the request specifies to do so.
|
The original issue: certain repository methods could produce very large result sets, which would internally break the narrowing and join flows and cause the operation to fail.
For example, if an entity has been extended, then after the initial query against the base BLC table, Hibernate does an additional query against the extension table using an IN
criteria containing each ID from the base result set.
If the base result set is too large, then the IN
clause may exceed query size limits imposed by DB platforms.
The repository methods identified as being susceptible to this problem have been given new custom implementations that collect the results in batches before returning to the caller.
The general pattern of changes is like so:
The affected repository method signature is migrated from the main *Repository
interface into a parent Customized*Repository
interface.
This is backwards compatible, as the customized repository interfaces are always parents of the main interfaces. Thus, the methods will still be available to components that are injecting the repository bean.
In the JpaCustomized*Repository
class, the method is implemented in a way that gathers results in batches.
Configuration properties are introduced which allow clients to customize the internal batch sizes for each method implementation.
Here is a list of affected repositories and repository methods:
ProductTagRepository
Please see the Product Tag repository configuration properties documentation
Updated methods
findAllByProductContextIdIn
findAllByProductContextIdInAndTagContextIdIn
findAllByProductContextId
findAllByProductContextIdAndVariantIdIsNull
findAllByTagContextIdIn
PromotionalCategoryProductRepository
Please see the Promotional Category Product repository configuration properties documentation
Updated methods
findAllByPromotionalProductContextIdIn
findAllByCategoryContextIdIn
PromotionalProductRepository
Please see the Promotional Product repository configuration properties documentation
Updated methods
findAllByProductContextIdIn
CategoryProductRepository
Please see the Category Product repository configuration properties documentation
Updated methods
findAllByCategoryContextIdIn
CategoryAssetRepository
Please see the Category Asset repository configuration properties documentation
Updated methods
findAllByCategoryContextIdIn
CategoryRepository
Please see the Category repository configuration properties documentation
Updated methods
findAllByParentCategoryContextId
OptionTemplateRepository
Note
|
As there was no pre-existing CustomizedOptionTemplateRepository interface or implementation, it was newly introduced as part of this effort.
|
Please see the Option Template repository configuration properties documentation
Updated methods
findAllByCategoryContextIdIn
Note
|
This change is important to fix issues with the Solr search narrowing. |
The ConsolidatedProduct domain has been updated to contain a few new fields which are utilized in the Solr search process. These fields have been added to aid in narrowing of results. See the 2.1.5 Search Release Notes for the updates made on search.
creatingApplicationId
Contains the ID of the application which owns the Product, if it exists. This will be null when a Product is created at the Tenant level.
This value is designed to be the same as the value in Tracking.creatingApplicationId
.
sandboxAwareCatalogOverrides
Contains Catalog override ids with additional Sandbox information encoded to aid with narrowing products that are both Sandbox and Catalog discriminated.
catalogAwareSandboxOverrides
Contains Sandbox override ids with additional Catalog information encoded to aid with narrowing products that are both Sandbox and Catalog discriminated.
Update DefaultConsolidatedProductPostProcessor
and DefaultProductConsolidationService
with changes to populate the above fields added onto ConsolidatedProduct
.
The method signature of DefaultProductConsolidationService#createPostProcessor
was updated as a part of this change, so clients who have overridden this method or are directly referencing it will need to update their references to match the new signature.
Update ProductConsolidationUtil
to fix a bug where a null
author could be passed or an incorrect author if a record contained changes from multiple authors.
Updated the Javadocs within ConsolidatedProduct
and DefaultConsolidatedProductPostProcessor
to more accurately and clearly describe some of the existing and new functionality.
Fixed an issue where after updating the maxConfigurableProductDepth property, any fetch information requests about a product that doesn’t have any addOns can lead to an error.
Bundle Item availability during Product consolidation for search indexing not really considering different sandbox versions for the consolidated products in question.
Fixed not indexing the priceWithDependentItems
for Merchandising products.
Previously, users were able to change the Option Type of an Option Template even after the template had been saved. This could lead to inconsistencies and unexpected behavior. The application now disallows updates to the Option Type once an Option Template has been saved.
Fixed that a product is not visible on storefront when there is a difference in product’s category hierarchy in application level vs tenant
Make option template’s allowed values grid orderable with drag’n’drop, also make option template’s allowed values grid column displayOrder readonly
Tip
|
The 2.x versions are Spring Boot 3 compatible. |
Add new otherMessagesMap
and otherItemMessagesMap
map fields to the MarketingMessageResponse
. These fields contain any marketing messages not matching the out-of-box location types.
Add fields messageType
and contentItemId
to MarketingMessage
domain.
Add contentItems
map field to the MarketingMessageResponse
Changes to the ExternalOfferProvider
:
Inject the ContentProvider
, that is used to fetch content items by id.
Introduce a hydrateContentItems
method to populate the content items map in the response.
TIP: The 2.x versions are Spring Boot 3 compatible.
Important
|
Depends on changes in Catalog Service 2.1.5 and Search Service 2.1.5. |
Introduced new DTO, SearchProductRequiredOptionInfo
, to represent a summary of information about the item choices for a Merchandising Product with enough information to allow creating priceable targets for them
Added method to Product matching the one in Catalog to calculate the price of the product with all required options and included products, getPriceWithDependentItems
This change affects both product search results and type ahead suggestions
To review the security related content, see Release Train 2.1.5 notes for the full details.
New Entity: Custom Content Field
This is a new entity that can define a reusable content field, which can be either an enumerated value or hydrated via a lookup operation.
A Custom Content Field can be created and configured in the new admin section, Content Management > Content Fields
These fields can be used in Content Models using the new “Reference” Field Type option
Now sort Content Items by Last Updated by default since that is visible in the grid
Fixed an error where "Undo Overrides" would accidentally delete the values of non-overriden content fields.
Updated DefaultContentItemService#removeFieldDataForContent
to exclude inherited records when fetching the collection of FieldData
to delete for a given ContentItem
. This way, we will only delete field data records that are directly related to the content item record that represents the override, rather that its parent’s.
Update the SkuInventoryService to better support RSQL filtering by implementing RsqlCrudEntityService
Fixed an issue where the same individual cart item (i.e. quantity of 1) could be reserved across different locations when the cart has other items only available in separate locations
SkuInventoryEndpoint#readAllSkuInventory should avoid filtering by name when no name parameter is provided
…
Refactored to improve extensibility and flexibility of cache invalidation logic. Event construction has been moved into protected methods to allow easier customization; additionally, more event data is passed through to give application listeners greater control over handling.
Fixed an issue where the Free Gift offer applied correctly but displayed an incorrect error message
The issue has been fixed where audit entries are missing from the list grid. The method for retrieving this data has been updated.
AlternateLifecycleSharedCodeAuditSummaryRepository#createOrUpdateAndIncrement(String, String, MonetaryAmount, ContextInfo) is deprecated
New column campaignId is added to BLC_SHARED_CODE_AUDIT_SUMMARY table
Add new otherMessagesMap
and otherItemMessagesMap
map fields to the MarketingMessageResponse
.
Introduced logic in the DefaultMarketingMessageResolver
populate the new map fields with any marketing messages that do not match the of the out-of-box LocationTypes
.
Code generation now ensures uniqueness across all generators within a campaign, not just within individual batches/generators.
Signature change in DefaultItemOfferProcessor#buildReducedFeeGiftDetail: the 3rd parameter Adjustment
has been added. This is an itemAdjustment of the FreeGiftItem, used to determine appropriate currency.
A new column - campaign_id
was added to the blc_shared_code_audit_summary
table, and it is now used to query data for the audit tab of the campaign. In order for the audit tab to work with historical data, you will need to populate it with data via a migration.
You can use the following example script for postgres to populate the campaign_id column. Adapt the script for your DB. Depending on the amount of the data in the blc_shared_code_audit_summary it might be worth disabling index and enabling it after migration.
DO
$$
DECLARE
batch_size INT := 10000;
last_id TEXT := '';
rows_updated INT;
BEGIN
LOOP
WITH cte AS (
SELECT s.id
FROM offer.blc_shared_code_audit_summary s
WHERE s.id > last_id
ORDER BY s.id
LIMIT batch_size
)
UPDATE offer.blc_shared_code_audit_summary s
SET campaign_id = oc.campaign_id
from offer.blc_offer_code oc, cte
where oc.id = s.offer_code_id and s.id = cte.id;
GET DIAGNOSTICS rows_updated = ROW_COUNT;
EXIT WHEN rows_updated = 0;
-- Advance last_id for next loop
SELECT MAX(id)
INTO last_id
FROM (SELECT s.id
FROM offer.blc_shared_code_audit_summary s
WHERE s.id > last_id
ORDER BY s.id
LIMIT batch_size) as si;
PERFORM pg_sleep(0.2); -- pause to reduce load
END LOOP;
END
$$;
Tip
|
The 2.x versions are Spring Boot 3 compatible. |
Fixed a gap in the payment transaction reversal job where asynchronous results were put in a FAILED_REVERSAL
management state.
This fix correctly handles asynchronous reversal transaction results and adds new reversal and management states to support this.
Additionally, this fix updates the webhook service to handle this scenario by checking for the new AWAITING_ASYNC_REVERSAL_RESULTS
management state, and updating the parent transaction’s management state based on the asynchronous results received via the webhook.
Enhanced webhook logging to include application and tenant IDs when there are exceptions around fetching and using the discriminated payment gateway properties.
No changes were made in PaymentTransactionService for this logging, all the changes were made in the respective gateway implementation libraries.
In case the transaction was marked as failed/canceled/reversal, and later a success received via a webhook for that transaction, it will update the transaction type and management state
Introduce new MODIFIED_TRANSACTION_STATUS
transaction management state
Method signature change for isTransactionRequiring3DSVerification and isHostedPaymentPageSetupTransaction in DefaultTransactionWebhookService - paymentResponse
is added a 3rd parameter
Disallow webhook to update archived payments
Fixed an issue where transactions executed via application-discriminated payment gateway properties failed reversal in the payment transaction reversal job.
Added a new applicationDiscriminationId
field on the Payment
to store the application ID on payment creation.
Updated the payment reversal job to use this new applicationDiscriminationId
when fetching application-discriminated payment gateway properties.
Non system payment management endpoints(CustomerPaymentManagementEndpoint) now cannot archive inaccessible existing payments in create requests
This version requires CatalogServices 2.1.5+ documented in the 2.1.5 Catalog Release Notes
Important
|
After upgrading to this version, a full reindex is required for the changes to take effect. |
This indexes the requiredOptionInfo
field on the ConsolidatedProduct
object.
This corresponds to changes in Catalog Service 2.1.5 introducing RequiredOptionInfo
to the ConsolidatedProduct
object.
{
"type_s":"PRODUCT",
"id":"01HZSSTPMDM70Q8FTZ6TNQFA54!CATALOG=TELCO_MASTER_CATALOG,SANDBOX=null,TRACKING_LEVEL=100000",
"contextId_s":"01HZSSTPMDM70Q8FTZ6TNQFA54",
"name_s":"Pay as You Go Bundle",
"name_t":"Pay as You Go Bundle",
"name_tta":"Pay as You Go Bundle",
"name_ttas":["Pay as You Go Bundle"],
"name_lower":"Pay as You Go Bundle",
"uri_s":"/bundles/pay-as-you-go",
"currency_currency":"EUR",
"price_money":0.0,
"businessType_s":"PHONE_FIRST_JOURNEY",
"businessType_t":"PHONE_FIRST_JOURNEY",
"name_s_fr":"Pay as You Go Bundle",
"name_t_fr":"Pay as You Go Bundle",
"name_tta_fr":"Pay as You Go Bundle",
"name_ttas_fr":["Pay as You Go Bundle"],
"name_lower_fr":"Pay as You Go Bundle",
"name_s_es":"Pay as You Go Bundle",
"name_t_es":"Pay as You Go Bundle",
"name_tta_es":"Pay as You Go Bundle",
"name_ttas_es":["Pay as You Go Bundle"],
"name_lower_es":"Pay as You Go Bundle",
"activeStartDate_dt":"2025-05-06T14:25:37.265Z",
"activeEndDate_dt":"9999-01-01T01:00:00Z",
"online_b":true,
"searchable_b":true,
"bestPrice_money":79.0,
// NEW //
"productType_s":"MERCHANDISING_PRODUCT",
// FIXED //
"productPricingPredicate_s":"{\"pricingKey\":\"01HZST1AY10PM1V7STWD8FJQP0\",\"productSku\":null,\"price\":{\"amount\":0.00,\"currency\":\"EUR\"},\"salePrice\":null,\"cost\":null,\"defaultSku\":null,\"lowestPricedSku\":null}",
// NEW //
"requiredOptionInfo_s":"[{\"pricingKey\":\"018FF39E-29DC-F683-F108-5744BB057A4C\",\"targetType\":\"CATEGORY\",\"minimumQuantity\":1,\"defaultChoiceUnderlyingSku\":\"223558\",\"defaultChoiceUnderlyingPricingKey\":\"01J361ESX5YPMF4DCZ8ENR0BKY\",\"defaultChoicePrice\":{\"amount\":79.00,\"currency\":\"EUR\"}},{\"pricingKey\":\"018FF3A0-1366-A366-B8F5-49CB5BB47FEF\",\"targetType\":\"CATEGORY\",\"minimumQuantity\":1,\"defaultChoicePrice\":{\"amount\":0.00,\"currency\":\"USD\"}}]",
"catalog_s":"TELCO_MASTER_CATALOG",
"trackingLevel_i":100000,
"tenant_s":"Telco",
"_version_":1831387928950996993
}
To facilitate indexing this data and returning on both search results and type ahead results, the following components were introduced:
RequiredOptionInfoSolrDocumentBuilderContributor
- This contributor adds the requiredOptionInfos_s
field to the Solr document.
RequiredOptionInfoSolrQueryContributor
- This contributor adds the requiredOptionInfos_s
field to the Solr query.
ProductRequiredOptionInfoResponseDecorator
- This decorator adds the requiredOptionInfos
field to the Product response DTO.
ProductTypeDocumentBuilderContributor
- This contributor adds the productType_s
field to the Product Type Solr document.
ProductTypeQueryContributor
- This contributor adds the productType_s
field to the Product Type Solr query.
ProductTypeResponseDecorator
- This decorator adds the requiredOptionInfos
field to the Product Type response DTO.
ProductTypeAheadSuggestionFieldsPostProcessor
- This post processor adds the productType
, requiredOptionInfos
, and productPricingPredicate
fields to typeahead responses.
This component will also set the id
so that the TypeAheadSuggestionFieldsPostProcessor
can merge related suggestions together.
This is a separate component from TypeAheadSuggestionFieldsPostProcessor
since it adds implicit fields specific to Products, which are Catalog entities whereas TypeAheadSuggestionFieldsPostProcessor
is used for all entities.
Added support for synonyms. Much of this code was already in place (API interactions, initial file loading), however it was not in use, not publicly documented, and no admin views existed to manage synonyms. As a result backwards compatibility has not been accounted for.
Updated synonym loading to support spaces in synonyms
Added managed language concept as properties. This requires a managed language to be defined in Solr. This is not equivalent to a locale but can use similar naming.
Updated synonym service and APIs to support managed language feature.
Add metadata for managing synonyms for products.
Note
|
This change is important to fix issues with the Solr search narrowing. |
Multiple Solr fields have been added to the Product Solr document to help with narrowing to the correct users, sandboxes, and catalogs.
These fields are introduced through the Solr Contributors CatalogTrackableSolrDocumentBuilderContributor
and SandboxTrackableSolrDocumentBuilderContributor
.
sandboxAwareCatalogOverrides_ss
This field contains the IDs of Catalog Overrides for a Product with additional sandbox information paired with the ID. For example, a Product will have a Catalog Override when a parent Catalog level product is updated in a child Catalog.
creatingApplicationId_s
This field contains an Application ID in which a Product was created.
If the Product is made in a Tenant context, then this value should be null
.
This value is designed to be the same as the value in Tracking.creatingApplicationId
.
catalogAwareSandboxOverrides_ss
This field contains the IDs of Sandbox Overrides for a Product with additional catalog information paired with the ID. For example, a Product will have a Sandbox Override when a promoted Product update is saved to a sandbox but not promoted.
catalogOmissions_ss
has also been added to the document in order to have a separate field for Catalog Omissions.
Previously omissions were only added as part of the catalogOverrides_ss
and the addition of a separate field enables more granular filtering.
CatalogTrackableSolrQueryContributor
and SandboxTrackableSolrQueryContributor
have been updated to filter against the new fields above.
The changes in the contributors fix narrowing in the search results, adding a more comprehensive set of filters. This prevents items from incorrectly being returned or missed in the search due to insufficient narrowing logic.
For example, given a set of Sandbox discriminated records, the extra logic utilizing the creatingApplicationId
field will narrow down the returned Product based on the appropriate creating application.
Marketplace applications have also been updated to utilize the new filters.
The signatures of the methods listed below were updated as a part of this change, so clients who have overridden or are directly referencing them will need to update their references to match the new signatures.
CatalogTrackableSolrQueryContributor#addMarketplaceFilter
CatalogTrackableSolrQueryContributor#filterOverriddenCatalogs
Refactored SandboxTrackableDocumentBuilderPreProcessor
to update sandboxOverrides
only in cases where the field was not already found within the Indexable
.
Updated the Javadocs for the added and updated narrowing fields to more accurately and clearly describe some of the existing and new functionality.
Fixed possible out-of-bounds errors or mis-serialization for Field Definitions where combined paths are enabled but the value on the Indexable
at the path is null.
Previously if a user had a combined path Field Definition and one of the property paths' values was null, the value was omitted.
If the user were to then try to parse the field value in a search response, then they would have no indication that one of the values is missing and could get index out of bounds errors or map values into the wrong DTO structure.
{
"multiValued": true,
"combined": true,
"multiValuedCombinedPropertyRootPath": "categories.*",
"propertyPaths": ["id", "nullable.value", "name"],
"delimiter": "||"
}
{
"categories": [
{
"id": 1,
"nullable": {
"value": 1
},
"name": "Category 1"
},
{
"id": 2,
"nullable": {
"value": null
},
"name": "Category 2"
}
]
}
[
"category1||1||Category 1",
"category2||||Category 2"
]
[
"category1||1||Category 1",
"category2||Category 2"
]
Using that example, it can be seen that if the user was trying to deserialize the response structure into a DTO, it would fail on the second result since it is missing the expected number of fields.
Fixed Product bestPrice
not accounting for priceWithDependentItems
for Merchandising Products.
Fixed productType
not being indexed and therefore not being returned on the Product in search results.
Fixed an issue where AbstractSolrQueryContributor beans could be initialized in an unexpected order by Spring when no explicit order was defined. The fix assigns specific ordering to ensure predictable execution for beans that depend on others, specifically DefaultSolrFacetContributor and DefaultSolrFilterContributor.
Improved resilience in the Indexer flow by adding timeouts and retries to service calls to Catalog(DefaultCatalogService#readBatchOfProducts) and Inventory(DefautlInventoryAvailabilityService#findConsolidatedInventoryAvailability), with proper backoff strategies. See properties broadleaf.indexer.inventory.service
and broadleaf.indexer.catalog.service
in Collections properties
Fixed an issue where searching for Products via a search term (i.e. "Sauce") in the Admin has the results sorted by ID instead of relevancy
Tip
|
The 2.x versions are Spring Boot 3 compatible. |
Validation for catalog removal was added that prevents deletion of catalogs that have references to other catalogs.
Improved Application Removal Validation: Applications can now only be removed if they have no assigned catalogs or marketplace catalogs.
Implicit Catalog Removal on Unassignment: When a catalog or marketplace catalog is unassigned from an application, its associated implicit catalog will also be removed.
New property gates this behavior
broadleaf.tenant.application.archive-unassigned-implicit-catalogs
, and is defaulted to true
Updated nexus
and noNexus
properties in TaxInformation
to be Iterables
instead of a string expecting a comma separated list of values
Updated CybersourceTaxProvider
to set the line item’s productName
as the tax item’s itemName
if it exists. Otherwise, fall back on the tax item’s skuCode
.
Upgraded the Stripe Java SDK version fom 22.3.0
to 29.1.0
.
Check updated guides for Element Integration, Mobile Pay and PaymentTransactionServices
Updated DefaultStripeExternalCallService
and DefaultStripeTransactionService
to use confirmation token for wallets and express checkout.
Enhanced webhook logging to include application and tenant IDs when there are exceptions around fetching and using the discriminated payment gateway properties.
Consume the X-Customer-Ref
header in CustomerSegmentContextInfoCustomizer
:
When provided, the deserialized CustomerRef
supersedes the authentication context when a CSR is acting as themselves.
When the user is not a CSR, this header is ignored if present in the request.
Fixes a bug where Quote pricing was being overridden by a CSR after making changes, removing any customer-segment based offers that apply to the quote owner.
The 2.x versions are Spring Boot 3 compatible.
JpaCustomizedCommonCatalogRepository
Include archived status when building members in inheritance line.
InheritanceLine(s)
Exclude archived catalogs when InheritanceLines#flatten is called. flatten(boolean) is available to include archived
Add InheritanceLine#getActiveMembers to only return unarchived catalogs in the hierarchy
DefaultTrackablePolicyUtils
Use InheritanceLines#getActiveMembers for getImplicitApplicationCatalog and matchInheritanceLine
JpaNumericIdNarrowExecutor
Filter archived catalogs in getIsolatedCatalogIdsFromApplication
DefaultJpaRsqlFilterRulesCriteriaBuilder
buildCatalogCriteria updated to use new InheritanceLines#getActiveMembers instead of #getMembers
Previously it was seen that when a Characteristic was created with no default value and then added to a Product, after promoting the change (not saving or deploying), the entire Product ended up set as the value of the Product Characteristic.
This was not the intended behavior, as the Product Characteristic’s value should have been set to null
instead.
The root issue was the logic during a promotion that is interested in setting back-references from the child entity to the parent entity interpreted the Product Characteristic’s value (which is Object
) as the same as the parent’s type, e.g., JpaProduct
in this case.
(This back-reference is being set because a promotion causes the production record to be copied, so the child reference needs to point to the copy not the original.)
After an entity with an embedded child entity was promoted and when reclaiming references on the promoted record, the parent entity was set as the value of null Object
fields of child entities.
This is only intended to happen when the child field’s type is the same as or is a subclass of the parent entity, indicating that it is a back-reference or otherwise part of a bidirectional relationship.
The check previously being made to detect this matching type was too general since it only checked that the child field’s type isAssignableFrom
the parent’s type, and, since the child field’s type was Object
, this was true.
The change made was to check if the child field’s class is Object
and then only set the parent reference if it is equal to Object.class
, which likely will never be true.
This preserves the original intent of setting entity back-references.
Fix the issue when a currency field can be lost (nulled) during a clone operation. A new ModelMapper converter has been introduced.
Fix the rare issue that messaging can be broken and not working silently after the startup.
Enhance the provider approach to use a single, unified API for interacting with other service APIs. To provide an approach to call the external API either via webclient, or via reflection. The reflection approach is qualified if the external API happens to be co-located in the same flexpackage == JPA Common Release Notes
Introduce LOOKUP
column type.
Added support for restricting routes by matching all security scopes not just any.
The previous default behavior when matching a Route’s security scopes was to match any.
ComponentRoute
now has a scopeMatchType
property that can be set to either ANY
or ALL
, with ANY
as the default.
Related changes have been made to Admin Metadata Service 2.1.4-GA.
@Bean
public ComponentRouteLocator productMetadataRoutes(RoutesBuilder routesBuilder) {
RoutesSpec routes = routesBuilder.routes()
.route("/products", r -> r.componentId("catalog:porducts:browse")
.scope("PRODUCT")
.scope("CUSTOM_ADMIN_VIEW_SCOPE")
.allMatchScopes(); // <-- NEW, default is `.anyMatchScopes()`
return routes.build();
}
Deprecate the Pricing Consumer Metadata Module
The use of this module produced unwanted coupling between separate consumer services and versions, so it was deprecated for ease of customization across the affected services' views.
Important: All the metadata contained in this module is now defined by the consumer services themselves.
Added new attributes to EntityGridView
to support restricting visibility of grids to tenant-only, application-only, or specific application contexts.
tenantOnly
: Boolean flag.
applicationOnly
: Boolean flag.
restrictionApplications
: Array of strings that are the IDs of the applications the grid is restricted to.
Updated SystemCurrencyContextHolder
to return USD as default when the JVM Locale does not map to a valid default currency.
Added a new generic structure to OrderDto domain to provide additional context to the offer engine. It is used for out of box offers such as dynamic amount offers and payment offers. It is intended to be used as a convenient extension point for customization needs between cart operations and the promotion engine.
New domain for payment gateway fulfillment callbacks
New interface methods for hosted page fulfillment callbacks
Fixed an issue in SimpleTaxProvider
to ensure that tax calculations are not done against configurations with a partial match.
Made the interface TranslationRepository
extend TrackableRsqlFilterExecutor
in order to use the RSQL service methods.
Fixed an NPE when attempting to map a translated value into an object within an embedded map collection where the untranslated object is null.
In particular, this was seen for Product Attributes when then Attribute was null in the default locale but had a translation added in a different locale.
The error message seen in the logs was java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "projection" is null
The fix allows missing entries (e.g., an Attribute) in maps to be instantiated and populated for the sake of translations.
This release contains security updates. For more details, please visit Broadleaf Security and review the security advisories page.