SAP Commerce Data Modeling Guide – Part 1
Fundamentals, Type System, Syntax, and Validation
Why Data Modeling Matters More Than You Think
When I first started working on SAP Commerce projects, I used to think data modeling was straightforward — define an itemtype, add some attributes, run ant clean all, and move on. It took a few real enterprise projects to realize that getting the data model wrong early can cost you weeks of rework later.
Every feature in SAP Commerce — OCC APIs, Backoffice screens, Solr indexing, approval workflows, order processing, and third-party integrations — sits on top of your data model. If that foundation is shaky, everything built on top of it becomes harder to maintain, harder to scale, and harder to debug.
This guide is written from real project experience. The goal is not just to show you the syntax, but to explain why things are done a certain way and what happens when they are not.
Where Do We Create items.xml?
Every custom extension in SAP Commerce has a resources/ folder where you define your data model. The file naming convention follows the extension name directly.
custom/ mycommercecore/ resources/ mycommercecore-items.xml src/ testsrc/
A real project example:
custom/bulkordercore/resources/bulkordercore-items.xml
Tip from experience: Keep your data model in a dedicated
*coreextension, not inside your storefront or facade extension. This keeps the model clean, reusable, and easy to share across multiple storefronts or integrations.
Basic Structure of items.xml
Every items.xml follows a specific structure. SAP Commerce expects sections in a defined order, and sticking to this order prevents confusing build errors.
<?xml version="1.0" encoding="ISO-8859-1"?><items xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="items.xsd"> <atomictypes> <!-- Primitive types – rarely customized --> </atomictypes> <collectiontypes> <!-- For multi-value attributes --> </collectiontypes> <enumtypes> <!-- Predefined constant values --> </enumtypes> <maptypes> <!-- Key-value pair structures --> </maptypes> <relations> <!-- Relationships between business objects --> </relations> <itemtypes> <!-- The core business entities --> </itemtypes></items>
Recommended Order
1. atomictypes2. collectiontypes3. enumtypes4. maptypes5. relations6. itemtypes
Why this order matters: SAP Commerce resolves references top-down during build. Defining an
enumtypeused in anitemtypeattribute after the itemtype will cause a build failure. Following this order prevents circular dependency errors and keeps the file readable.
Understanding the SAP Commerce Type System
Before diving into each section, it helps to understand the big picture.
SAP Commerce does not expect you to write SQL or create database tables manually. Instead, you define your business model in items.xml, and the platform takes care of generating everything else — database tables, Java model classes, relation handlers, Backoffice UI integration, and type metadata.
This abstraction is called the Type System.
items.xml definition → Database table (e.g., BulkOrders) → Model class (e.g., BulkOrderModel.java) → Jalo class → Type metadata (visible in HAC / Backoffice) → FlexibleSearch support
The benefit of this approach is consistency. Your model is the single source of truth across the database layer, service layer, API layer, and Backoffice — all from one XML file.
The Six Building Blocks
1. atomictypes — Primitive Types
atomictypes represent the lowest-level value types. These are almost always built-in Java primitives.
You will use them constantly as attribute types, but you will rarely define new ones.
Built-in types used most often:
| Type | Used For |
|---|---|
java.lang.String | Codes, names, descriptions |
java.lang.Integer | Quantities, counts, retry limits |
java.lang.Boolean | Flags, toggles |
java.util.Date | Timestamps, scheduled dates |
java.lang.Double | Prices, weights, rates |
java.lang.Long | Large IDs, counters |
Real project example:
BulkOrder.bulkOrderCode → java.lang.StringBulkOrder.retryCount → java.lang.IntegerBulkOrder.active → java.lang.BooleanBulkOrder.processedTime → java.util.Date
From experience: You almost never need to define custom atomic types. If you find yourself thinking about it, there is usually a better approach using an enum or a composed itemtype.
2. collectiontypes — Multi-Value Attributes
collectiontypes are used when a single attribute needs to hold multiple values of the same type.
<collectiontypes> <collectiontype code="BulkOrderFileList" elementtype="Media" type="list"/></collectiontypes>
Syntax breakdown:
| Attribute | Meaning |
|---|---|
code | Name of the collection type |
elementtype | The type of each element inside |
type | list, set, or collection |
Choosing the right collection type:
| Type | When to Use |
|---|---|
list | Order matters (e.g., processing steps, file history) |
set | No duplicates allowed (e.g., carrier codes, permission flags) |
collection | Generic use when order and uniqueness don’t matter |
Real project example:
BulkOrder → successFiles (list of Media) → errorFiles (list of Media) → processedFiles (list of Media)
Common mistake: Using a
listwhen you actually need asetcan lead to duplicate entries creeping into your data over time, especially in async processing scenarios. Choose deliberately.
Performance note: Large collections stored directly on an item can cause slow Backoffice rendering. If a collection might grow beyond a few hundred entries, consider a proper relation with its own itemtype instead.
3. enumtypes — Controlled Constant Values
enumtypes are one of the most useful modeling tools in SAP Commerce. They define a fixed set of allowed values for an attribute, preventing invalid data from ever entering your system.
<enumtypes> <enumtype code="BulkOrderStatus" autocreate="true" generate="true"> <value code="PENDING"/> <value code="VALIDATED"/> <value code="PROCESSING"/> <value code="FAILED"/> <value code="COMPLETED"/> </enumtype></enumtypes>
Why enums beat plain strings:
Imagine a developer sets a status as "Fail". Another sets it as "FAILED". A third uses "failure". Now your FlexibleSearch queries break, your Backoffice filters show incorrect results, and your integration partner gets inconsistent data.
Enums eliminate this entirely. The compiler enforces valid values, your IDE gives you autocomplete, and your model stays clean.
Common use cases in enterprise projects:
- Order status, approval status, payment status
- Bulk upload validation status
- Document processing stages
- Procurement report lifecycle states
- Notification types
Adding enum values later:
You can safely add new <value> entries to an existing enum after deployment by running a system update. However, never remove or rename enum values that already have data associated with them in the database — this will cause runtime errors.
4. maptypes — Key-Value Pair Storage
maptypes are useful when you need to store flexible, dynamic key-value pairs on a business object.
<maptypes> <maptype code="StringToStringMap" argumenttype="java.lang.String" returntype="java.lang.String"/></maptypes>
Real project examples:
Country → Currency US → USD IN → INR DE → EURWarehouse code → Display name WH_LONDON → London Central Warehouse WH_BERLIN → Berlin Distribution Hub
When map types make sense:
- The keys are dynamic and not known at design time
- Configuration needs to vary per record
- You need to store runtime metadata or API response mappings
- Localization or region-specific overrides
Important: Map types are stored as serialized data in the database. This means you cannot use FlexibleSearch to query inside a map. If you ever need to search by a key or value, use a proper related itemtype instead.
5. relations — Connecting Business Objects
Relations define how your itemtypes are connected to each other. This is where most of the real design decisions happen in enterprise modeling.
<relations> <relation code="BulkOrder2B2BUnit" autocreate="true" generate="true"> <sourceElement type="B2BUnit" qualifier="bulkOrders" cardinality="one"/> <targetElement type="BulkOrder" qualifier="b2bUnit" cardinality="many"/> </relation></relations>
Understanding the structure:
| Element | Meaning |
|---|---|
sourceElement | The parent / owning side |
targetElement | The child / dependent side |
qualifier | The generated attribute name on the model |
cardinality | one or many |
Common enterprise relations:
B2BUnit → BulkOrders (one-to-many)Order → OrderEntries (one-to-many)Product → Categories (many-to-many)Warehouse → CarrierPreferences (one-to-many)
One-to-many vs many-to-many:
One-to-many relations are stored as a foreign key column on the target table — clean and efficient. Many-to-many relations require a separate join table, which adds query complexity and maintenance overhead. Do not create many-to-many relations unless your business model genuinely requires them.
Key design principle: Model relations based on business ownership and access patterns, not based on what looks convenient in Backoffice. Ask yourself: “Who owns this relationship? How will this be queried?” The answers should drive your design.
6. itemtypes — The Core Business Entities
itemtypes define the actual business objects your application works with. Everything else in items.xml is in service of this section.
<itemtypes> <itemtype code="BulkOrder" extends="GenericItem" autocreate="true" generate="true"> <deployment table="BulkOrders" typecode="12001"/> <attributes> <attribute qualifier="bulkOrderCode" type="java.lang.String"> <persistence type="property"/> <modifiers optional="false" unique="true" search="true"/> </attribute> <attribute qualifier="status" type="BulkOrderStatus"> <persistence type="property"/> <modifiers optional="false" search="true"/> </attribute> <attribute qualifier="uploadedByUser" type="Employee"> <persistence type="property"/> <modifiers optional="true"/> </attribute> </attributes> </itemtype></itemtypes>
Attribute breakdown:
| Attribute | Meaning |
|---|---|
code | The itemtype name (becomes model class name) |
extends | Parent type |
autocreate | Creates the database table automatically |
generate | Generates Java model classes |
Choosing the Right Parent Type
One of the most impactful decisions in itemtype design is choosing what to extend. This determines what attributes and behaviors your type inherits automatically.
| Parent Type | When to Use |
|---|---|
GenericItem | General-purpose custom business objects |
User | Custom user or employee extensions |
Product | Product extensions (adds catalog-awareness) |
Order | Order extensions |
AbstractOrder | When sharing logic between Order and Cart |
CMSItem | CMS-managed content items |
From experience: Extending
ProductorOrdergives you a lot of inherited behavior for free — but it also adds database overhead and Solr indexing complexity. If your object is loosely related to a product (e.g., a product document or a warranty record), you may be better off extendingGenericItemand creating an explicit relation toProduct.
Deployment — Database Table and Type Code
Every custom itemtype that needs its own database table requires a <deployment> entry.
<deployment table="BulkOrders" typecode="12001"/>
Rules you must follow:
typecodemust be unique across the entire system — including SAP’s own built-in types. Checkcore-items.xmland other extension files before picking a number.- Most enterprise projects reserve a range (e.g., 10000–19999) for custom types and document this in a team registry.
- Never change the
typecodeafter deployment — it breaks the type registry and can corrupt existing data. - Never change the
tablename once data exists in production — you will orphan all existing records.
Practical advice: Create a shared document or wiki page in your team listing all deployed typecodes. It takes five minutes to set up and saves enormous pain when two developers accidentally pick the same number.
Understanding Attributes in Detail
Attributes are the properties that make up your business object. Each attribute has three key parts: the type, the persistence strategy, and the modifiers.
Persistence Types
Property persistence stores the value directly in the database column:
<persistence type="property"/>
This is what you use for the vast majority of attributes.
Dynamic attributes calculate their value at runtime — nothing is stored in the database:
<attribute qualifier="displayStatus" type="java.lang.String"> <persistence type="dynamic" attributeHandler="bulkOrderStatusHandler"/></attribute>
Dynamic Attribute Handlers — How They Work
A dynamic attribute handler is a Spring-managed bean that computes the attribute value on the fly.
@Component("bulkOrderStatusHandler")public class BulkOrderStatusHandler implements DynamicAttributeHandler<String, BulkOrderModel> { @Override public String get(final BulkOrderModel model) { if (model.getErrorFile() != null) { return "FAILED"; } if (model.getSuccessFile() != null) { return "COMPLETED"; } return "PENDING"; } @Override public void set(final BulkOrderModel model, final String value) { // Leave empty if the attribute is read-only }}
When to use dynamic attributes:
- Computed flags or derived status values
- Display labels that combine multiple fields
- Runtime aggregations
- Values that change frequently and don’t need historical storage
Performance warning: Dynamic attributes are calculated every time the model is accessed. If your handler has database queries, external API calls, or heavy logic, it will slow down Backoffice rendering, OCC API responses, and FlexibleSearch results. Keep handler logic lightweight, and cache values where it makes sense.
Modifiers — Controlling Attribute Behavior
<modifiers optional="false" unique="true" read="true" write="true" search="true"/>
| Modifier | What it does |
|---|---|
optional="false" | Makes the field mandatory — cannot be saved as null |
unique="true" | Prevents duplicate values across all instances |
read="true" | Allows reading the attribute |
write="true" | Allows writing to the attribute |
search="true" | Makes the attribute available in FlexibleSearch queries |
initial="true" | Value can only be set at creation time, never updated after |
Real scenario: The
initialmodifier is very useful for audit fields. If you have asubmittedByattribute that should never be changed after the order is created, setinitial="true". This prevents accidental overwrites even through service calls.
Localized Attributes — Multi-Language Support
When you need attribute values to vary by language — product names, descriptions, labels — use the localized: prefix:
<attribute qualifier="displayName" type="localized:java.lang.String"> <persistence type="property"/></attribute>
SAP Commerce stores each language variant separately in the database and retrieves the correct one based on the active session language.
Indexes — Dramatically Improving Query Performance
This is a section many beginners skip entirely, and they pay for it in production.
If you query an attribute frequently in FlexibleSearch — especially in list views, filters, or batch jobs — add an index:
<itemtype code="BulkOrder" extends="GenericItem" ...> <deployment table="BulkOrders" typecode="12001"/> <indexes> <index name="bulkOrderCodeIdx"> <key attribute="bulkOrderCode"/> </index> <index name="statusIdx"> <key attribute="status"/> </index> <index name="b2bUnitStatusIdx"> <key attribute="b2bUnit"/> <key attribute="status"/> </index> </indexes> <attributes> ... </attributes></itemtype>
Indexing guidelines:
- Index any attribute used in a
WHEREclause of common queries - Consider composite indexes for attributes that are always queried together (e.g.,
b2bUnit + status) - Do not over-index — each index adds write overhead and storage cost
- Attributes marked
unique="true"get an implicit index automatically
From experience: On a project handling 100,000+ bulk order records, adding a composite index on
(b2bUnit, status)reduced a Backoffice list view query from 8 seconds to under 200ms. Indexes matter enormously at scale.
A Complete Real-World itemtype Example
Here is what a well-modeled itemtype looks like in a real enterprise project — combining everything above:
<itemtype code="BulkOrder" extends="GenericItem" autocreate="true" generate="true"> <deployment table="BulkOrders" typecode="12001"/> <indexes> <index name="bulkOrderCodeIdx"> <key attribute="bulkOrderCode"/> </index> <index name="b2bUnitStatusIdx"> <key attribute="b2bUnit"/> <key attribute="status"/> </index> </indexes> <attributes> <!-- Business key - must be unique --> <attribute qualifier="bulkOrderCode" type="java.lang.String"> <persistence type="property"/> <modifiers optional="false" unique="true" search="true"/> </attribute> <!-- Lifecycle status using enum for type safety --> <attribute qualifier="status" type="BulkOrderStatus"> <persistence type="property"/> <modifiers optional="false" search="true"/> </attribute> <!-- Who uploaded this order - set once, never changed --> <attribute qualifier="uploadedByUser" type="Employee"> <persistence type="property"/> <modifiers optional="true" initial="true"/> </attribute> <!-- Uploaded file reference --> <attribute qualifier="sourceFile" type="Media"> <persistence type="property"/> <modifiers optional="true"/> </attribute> <!-- Validation result files --> <attribute qualifier="successFile" type="Media"> <persistence type="property"/> <modifiers optional="true"/> </attribute> <attribute qualifier="errorFile" type="Media"> <persistence type="property"/> <modifiers optional="true"/> </attribute> <!-- How many times we retried processing --> <attribute qualifier="retryCount" type="java.lang.Integer"> <persistence type="property"/> <modifiers optional="true"/> </attribute> <!-- Calculated display value - not stored in DB --> <attribute qualifier="displayStatus" type="java.lang.String"> <persistence type="dynamic" attributeHandler="bulkOrderDisplayStatusHandler"/> </attribute> </attributes></itemtype>
The Build and Deployment Flow
After every change to items.xml, you need to follow this flow:
1. Save changes to items.xml2. Run: ant clean all3. Start the SAP Commerce server4. Run system update (HAC → Platform → Update)5. Validate in Backoffice and HAC
What system update does:
- Creates or alters database tables
- Registers new itemtypes in the type system
- Creates or updates indexes
- Rebuilds relation metadata
Important: System update is incremental — it adds new columns and tables but does not remove old ones. If you rename an attribute, both the old column and new column will exist in the database. Clean up carefully.
Validating Your Model After Deployment
After deployment, do not assume everything worked. Validate each piece explicitly.
In HAC (Administration Console):
Navigate to Console → Scripting Console and run:
import de.hybris.platform.core.Registrydef typeService = Registry.applicationContext.getBean("typeService")def composedType = typeService.getComposedTypeForCode("BulkOrder")println composedType.allAttributeDescriptors*.qualifier
This confirms that all your attributes are registered in the type system.
FlexibleSearch validation:
SELECT {pk}, {bulkOrderCode}, {status} FROM {BulkOrder}
Run this in HAC → FlexibleSearch to confirm the table exists and attributes are queryable.
Backoffice validation checklist:
| Check | Where |
|---|---|
| Type appears | System → Types → search “BulkOrder” |
| Attributes visible | Open a BulkOrder instance, check all fields |
| Enum values correct | Check dropdown shows all defined values |
| Relations navigable | Navigate from B2BUnit → BulkOrders |
| Dynamic attributes load | Check no errors in Backoffice log on open |
Common Mistakes and How to Avoid Them
1. Duplicate Typecodes
Two developers define typecodes 12001 independently. The build succeeds but one model silently overrides the other. Always maintain a team-wide typecode registry.
2. Missing Unique Constraints
Without unique="true" on your business key, you can end up with two BulkOrder records with the same bulkOrderCode. This causes chaos in integrations and reports. Always enforce uniqueness on business keys.
3. Heavy Logic in Dynamic Attribute Handlers
A handler that runs a FlexibleSearch query for every row in a Backoffice list view means hundreds of database queries per page load. Keep handlers fast — compute from already-loaded model data where possible.
4. Changing Typecodes in Production
This is one of the most dangerous changes you can make. The typecode is stored in the database against every existing record. Changing it will cause the platform to lose track of all existing instances of that type.
5. Unnecessary Many-to-Many Relations
Every many-to-many relation creates a join table, complicates FlexibleSearch queries, and adds maintenance cost. Before creating one, ask whether one side truly needs a collection, or whether a one-to-many with a well-placed relation is sufficient.
6. Skipping Indexes on Searchable Attributes
Every attribute marked search="true" and used in FlexibleSearch WHERE clauses should be indexed. This is not optional at enterprise scale.
7. Ignoring the items.xml Order
Defining an enumtype below the itemtype that uses it causes build failures. Always follow the recommended section order.
Summary
Here is what good SAP Commerce data modeling looks like in practice:
- Use enumtypes for any attribute with a fixed set of allowed values
- Use property persistence by default, and dynamic attributes only for computed values with lightweight logic
- Always set a unique constraint on your primary business key
- Index attributes that appear in FlexibleSearch WHERE clauses
- Choose collection types deliberately —
listvssethas real consequences - Model relations based on business ownership, not Backoffice convenience
- Maintain a team-wide typecode registry to prevent conflicts
- Never change typecodes or deployment table names after production data exists
- Validate everything in Backoffice, HAC, and FlexibleSearch after every deployment
Good data modeling is not about making the application work today. It is about making sure it still works cleanly six months from now, when the team has grown, the data volume has scaled, and the business requirements have evolved.
What’s Coming in Part 2
In the next part of this guide, we will go deeper into enterprise-grade modeling:
- Directional vs bidirectional relations and when each is appropriate
- B2BUnit-driven architecture patterns
- Advanced FlexibleSearch optimization for complex models
- Solr indexing strategy for custom itemtypes
- Real enterprise use cases — bulk ordering, procurement, document management
- Multi-extension model sharing and dependency management
Have questions or a scenario you want covered? Drop a comment below — happy to discuss real project challenges.

Leave a comment