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 *core extension, 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. atomictypes
2. collectiontypes
3. enumtypes
4. maptypes
5. relations
6. itemtypes

Why this order matters: SAP Commerce resolves references top-down during build. Defining an enumtype used in an itemtype attribute 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:

TypeUsed For
java.lang.StringCodes, names, descriptions
java.lang.IntegerQuantities, counts, retry limits
java.lang.BooleanFlags, toggles
java.util.DateTimestamps, scheduled dates
java.lang.DoublePrices, weights, rates
java.lang.LongLarge IDs, counters

Real project example:

BulkOrder.bulkOrderCode → java.lang.String
BulkOrder.retryCount → java.lang.Integer
BulkOrder.active → java.lang.Boolean
BulkOrder.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:

AttributeMeaning
codeName of the collection type
elementtypeThe type of each element inside
typelist, set, or collection

Choosing the right collection type:

TypeWhen to Use
listOrder matters (e.g., processing steps, file history)
setNo duplicates allowed (e.g., carrier codes, permission flags)
collectionGeneric 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 list when you actually need a set can 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 → EUR
Warehouse 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:

ElementMeaning
sourceElementThe parent / owning side
targetElementThe child / dependent side
qualifierThe generated attribute name on the model
cardinalityone 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:

AttributeMeaning
codeThe itemtype name (becomes model class name)
extendsParent type
autocreateCreates the database table automatically
generateGenerates 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 TypeWhen to Use
GenericItemGeneral-purpose custom business objects
UserCustom user or employee extensions
ProductProduct extensions (adds catalog-awareness)
OrderOrder extensions
AbstractOrderWhen sharing logic between Order and Cart
CMSItemCMS-managed content items

From experience: Extending Product or Order gives 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 extending GenericItem and creating an explicit relation to Product.


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:

  • typecode must be unique across the entire system — including SAP’s own built-in types. Check core-items.xml and 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 typecode after deployment — it breaks the type registry and can corrupt existing data.
  • Never change the table name 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"/>
ModifierWhat 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 initial modifier is very useful for audit fields. If you have a submittedBy attribute that should never be changed after the order is created, set initial="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 WHERE clause 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.xml
2. Run: ant clean all
3. Start the SAP Commerce server
4. 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.Registry
def 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:

CheckWhere
Type appearsSystem → Types → search “BulkOrder”
Attributes visibleOpen a BulkOrder instance, check all fields
Enum values correctCheck dropdown shows all defined values
Relations navigableNavigate from B2BUnit → BulkOrders
Dynamic attributes loadCheck 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 deliberatelylist vs set has 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