Thursday, August 27, 2026

SAP Commerce Custom OCC API Development: Complete Step-by-Step Java Guide

Introduction

In the previous article, we discussed the architecture of SAP Commerce OCC Web Services.

We saw the typical request flow:

Client
   ↓
OCC Controller
   ↓
Facade
   ↓
Service
   ↓
DAO
   ↓
FlexibleSearch
   ↓
Database

Today, instead of only discussing the architecture, we are going to build a custom OCC API.

We will create an API that retrieves a product by product code.

Our final API will look like:

GET /occ/v2/electronics/customproducts/P100

and return:

{
  "code": "P100",
  "name": "Laptop",
  "description": "Business Laptop"
}

This is the kind of practical knowledge that is especially useful when working on real SAP Commerce projects.

SAP's current documentation recommends using OCC Extensions to extend OCC functionality. OCC extensions contain REST controllers and related classes in /src, while their web Spring configuration is placed under /resources/occ/v2/<extension>/web/spring.


1. What Are We Going to Build?

Our custom API will follow this architecture:

                    REST CLIENT
                         |
                         ↓
              CustomProductController
                         |
                         ↓
                CustomProductFacade
                         |
                         ↓
                CustomProductService
                         |
                         ↓
                  CustomProductDao
                         |
                         ↓
                 FlexibleSearch
                         |
                         ↓
                    Database

Response:

Database
   ↓
ProductModel
   ↓
Service
   ↓
Facade
   ↓
Converter
   ↓
ProductData
   ↓
ProductWsDTO
   ↓
JSON

2. Prerequisites

Before implementing the API, you should have:

  • SAP Commerce installed
  • A custom SAP Commerce extension
  • Java knowledge
  • Spring knowledge
  • Basic FlexibleSearch knowledge
  • Basic OCC knowledge
  • Postman or another REST client

3. Create an OCC Extension

For this example, let's assume our extension is:

customocc

SAP's OCC extension naming convention requires the extension name to end with occ.

For example:

xyzocc
customocc
mycompanyocc

SAP documents generating OCC extensions using the yocc extension template and adding the resulting extension to localextensions.xml.


4. Add the Extension to localextensions.xml

Open:

config/localextensions.xml

Add:

<extension name="customocc"/>

For example:

<extensions>

    <extension name="commercewebservices"/>

    <extension name="customcore"/>
    <extension name="customfacades"/>
    <extension name="customocc"/>

</extensions>

The exact dependency structure depends on your project.


5. OCC Extension Directory Structure

A typical OCC extension looks like:

customocc
│
├── extensioninfo.xml
│
├── resources
│   │
│   └── occ
│       └── v2
│           └── customocc
│               │
│               └── web
│                   └── spring
│                       └── customocc-web-spring.xml
│
└── src
    │
    └── com
        └── mycompany
            └── customocc
                │
                └── controllers

This structure is important.

SAP's documentation specifies that the Spring configuration should be under:

/resources/occ/v2/<extension_name>/web/spring

and the configuration filename should end with:

-web-spring.xml


6. Create the DAO

Our DAO will retrieve a product using FlexibleSearch.

Create:

public interface CustomProductDao
{
    ProductModel findProductByCode(String code);
}

Implementation:

public class DefaultCustomProductDao
        implements CustomProductDao
{
    @Resource
    private FlexibleSearchService flexibleSearchService;

    @Override
    public ProductModel findProductByCode(
            final String code)
    {
        final String query =
                "SELECT {p.pk} " +
                "FROM {Product AS p} " +
                "WHERE {p.code} = ?code";

        final FlexibleSearchQuery searchQuery =
                new FlexibleSearchQuery(query);

        searchQuery.addQueryParameter(
                "code",
                code);

        final SearchResult<ProductModel> result =
                flexibleSearchService.search(searchQuery);

        return result.getResult()
                .stream()
                .findFirst()
                .orElse(null);
    }
}

The DAO has one responsibility:

Retrieve the data.

It should not contain presentation logic.


7. Create the Service

Now create the service interface:

public interface CustomProductService
{
    ProductModel getProductByCode(String code);
}

Implementation:

public class DefaultCustomProductService
        implements CustomProductService
{
    @Resource
    private CustomProductDao customProductDao;

    @Override
    public ProductModel getProductByCode(
            final String code)
    {
        return customProductDao
                .findProductByCode(code);
    }
}

The architecture is now:

Service
   ↓
DAO
   ↓
FlexibleSearch

8. Why Do We Need the Service Layer?

A common mistake is:

Controller
   ↓
DAO

Instead, we use:

Controller
   ↓
Facade
   ↓
Service
   ↓
DAO

Why?

Because business logic belongs in the Service Layer.

For example, tomorrow we might introduce:

Only APPROVED products
Only products from a specific catalog
Customer-specific pricing
Stock validation
Product visibility rules

That logic can be handled by the Service Layer.


9. Create the Data Object

Now we need an object to carry product information.

For example:

public class CustomProductData
{
    private String code;
    private String name;
    private String description;

    public String getCode()
    {
        return code;
    }

    public void setCode(final String code)
    {
        this.code = code;
    }

    public String getName()
    {
        return name;
    }

    public void setName(final String name)
    {
        this.name = name;
    }

    public String getDescription()
    {
        return description;
    }

    public void setDescription(
            final String description)
    {
        this.description = description;
    }
}

This object is different from:

ProductModel

The model represents the SAP Commerce item.

The Data object represents the information we want to expose through the application layer.


10. Create the Converter

SAP Commerce uses converters and populators extensively to construct Data objects from Models or other service-layer objects. SAP's documentation recommends Spring-configured converters and keeping conversion logic inside populators.

Create:

public class CustomProductPopulator
        implements Populator<ProductModel,
                             CustomProductData>
{
    @Override
    public void populate(
            final ProductModel source,
            final CustomProductData target)
    {
        target.setCode(source.getCode());
        target.setName(source.getName());
        target.setDescription(
                source.getDescription());
    }
}

11. Why Use a Populator?

Suppose tomorrow we want to add:

Price
Stock
Images
Categories
Classification
Reviews
Promotions

Instead of putting everything into one huge class, we can create:

CustomProductBasicPopulator
CustomProductPricePopulator
CustomProductStockPopulator
CustomProductImagePopulator

This keeps the conversion logic modular.

SAP specifically describes populators as a pipeline of population tasks and notes that configurable populators can help avoid unnecessary conversion and reduce performance/bandwidth costs.


12. Create the Facade

Create:

public interface CustomProductFacade
{
    CustomProductData getProductByCode(
            String code);
}

Implementation:

public class DefaultCustomProductFacade
        implements CustomProductFacade
{
    @Resource
    private CustomProductService customProductService;

    @Resource
    private Converter<ProductModel,
                       CustomProductData> customProductConverter;

    @Override
    public CustomProductData getProductByCode(
            final String code)
    {
        final ProductModel product =
                customProductService
                        .getProductByCode(code);

        if (product == null)
        {
            return null;
        }

        return customProductConverter
                .convert(product);
    }
}

Now:

Facade
   ↓
Service
   ↓
DAO

13. Create the WsDTO

Now create the web-service DTO.

public class CustomProductWsDTO
{
    private String code;
    private String name;
    private String description;

    public String getCode()
    {
        return code;
    }

    public void setCode(final String code)
    {
        this.code = code;
    }

    public String getName()
    {
        return name;
    }

    public void setName(final String name)
    {
        this.name = name;
    }

    public String getDescription()
    {
        return description;
    }

    public void setDescription(
            final String description)
    {
        this.description = description;
    }
}

The purpose is to keep the API contract separate from internal SAP Commerce models.

SAP's OCC implementation documentation identifies WsDTO as the data layer used by the OCC REST API.


14. Create the OCC Controller

Now we get to the most important part.

Create:

@Controller
@RequestMapping(
        value = "/{baseSiteId}/customproducts")
public class CustomProductController
{
    @Resource
    private CustomProductFacade customProductFacade;

    @RequestMapping(
            value = "/{code}",
            method = RequestMethod.GET)
    @ResponseBody
    public CustomProductWsDTO getProduct(
            @PathVariable final String code)
    {
        final CustomProductData productData =
                customProductFacade
                        .getProductByCode(code);

        return convertToWsDTO(productData);
    }

    protected CustomProductWsDTO convertToWsDTO(
            final CustomProductData data)
    {
        final CustomProductWsDTO dto =
                new CustomProductWsDTO();

        dto.setCode(data.getCode());
        dto.setName(data.getName());
        dto.setDescription(
                data.getDescription());

        return dto;
    }
}

SAP's official OCC extension example follows this general pattern: a controller is created under /src, mapped using Spring MVC annotations, and returns a WsDTO.


15. Our API URL

The controller:

@RequestMapping(
    value = "/{baseSiteId}/customproducts")

combined with:

@RequestMapping(
    value = "/{code}",
    method = RequestMethod.GET)

gives:

/occ/v2/{baseSiteId}/customproducts/{code}

For example:

/occ/v2/electronics/customproducts/P100

SAP documents /occ/v2 as the standard web root for commercewebservices.


16. Configure the Spring Beans

Now we need to tell Spring about our beans.

Create:

customocc-web-spring.xml

under:

resources/occ/v2/customocc/web/spring/

Example:

<bean id="customProductDao"
      class="com.mycompany.customocc.dao.impl.DefaultCustomProductDao">
</bean>

<bean id="customProductService"
      class="com.mycompany.customocc.service.impl.DefaultCustomProductService">
    <property name="customProductDao"
              ref="customProductDao"/>
</bean>

<bean id="customProductPopulator"
      class="com.mycompany.customocc.populator.CustomProductPopulator">
</bean>

<bean id="customProductConverter"
      class="de.hybris.platform.servicelayer.dto.converter.impl.AbstractPopulatingConverter">
    <property name="targetClass"
              value="com.mycompany.customocc.data.CustomProductData"/>

    <property name="populators">
        <list>
            <ref bean="customProductPopulator"/>
        </list>
    </property>
</bean>

<bean id="customProductFacade"
      class="com.mycompany.customocc.facade.impl.DefaultCustomProductFacade">
    <property name="customProductService"
              ref="customProductService"/>

    <property name="customProductConverter"
              ref="customProductConverter"/>
</bean>

Note: In a real project, you would normally separate beans into the appropriate core/facades/OCC extensions rather than placing every layer inside the OCC extension. This example keeps the article easy to understand.


17. Build the Project

After creating the extension and configuration:

ant clean all

Then restart the SAP Commerce server.

For a local environment, you may use your normal SAP Commerce startup process.


18. Test the API Using Postman

Open Postman.

Select:

GET

Enter:

https://localhost:9002/occ/v2/electronics/customproducts/P100

Depending on your local SSL configuration, you may use:

http://localhost:9001/occ/v2/electronics/customproducts/P100

Use the port configured in your environment.


19. Authentication

If the endpoint is secured, obtain an appropriate OAuth access token and add:

Authorization: Bearer <access-token>

SAP Commerce OCC uses configurable Spring Security mechanisms for securing OCC calls.


20. Expected Response

If product P100 exists:

{
    "code": "P100",
    "name": "Laptop",
    "description": "Business Laptop"
}

The complete flow is:

GET /occ/v2/electronics/customproducts/P100
                    |
                    ↓
          CustomProductController
                    |
                    ↓
           CustomProductFacade
                    |
                    ↓
           CustomProductService
                    |
                    ↓
             CustomProductDao
                    |
                    ↓
             FlexibleSearch
                    |
                    ↓
              ProductModel
                    |
                    ↓
                Converter
                    |
                    ↓
             ProductData
                    |
                    ↓
                WsDTO
                    |
                    ↓
                  JSON

21. Handling Product Not Found

The current example returns null.

In a production API, this is not ideal.

Instead, we should throw a meaningful exception.

For example:

if (product == null)
{
    throw new UnknownIdentifierException(
            "Product not found: " + code);
}

Then the OCC error-handling mechanism can generate an appropriate error response.

For example:

{
    "errors": [
        {
            "type": "UnknownIdentifierError",
            "message": "Product not found: P999"
        }
    ]
}

SAP Commerce provides an OCC error-response mechanism specifically for converting exceptions into REST responses.


22. Bad Request vs Not Found

A good API should distinguish errors.

Invalid request

400 Bad Request

Authentication failure

401 Unauthorized

Access denied

403 Forbidden

Resource doesn't exist

404 Not Found

Server error

500 Internal Server Error

23. Don't Put FlexibleSearch in Controller

Avoid:

@Controller
public class ProductController
{
    @RequestMapping(...)
    public ProductWsDTO getProduct(...)
    {
        // FlexibleSearch here
    }
}

This creates tight coupling.

Instead:

Controller
    ↓
Facade
    ↓
Service
    ↓
DAO

This is easier to:

  • Test
  • Maintain
  • Extend
  • Debug
  • Reuse

24. Don't Return ProductModel

Avoid:

public ProductModel getProduct(...)

from the API.

Prefer:

ProductModel
     ↓
ProductData
     ↓
ProductWsDTO

This prevents internal persistence structures from becoming your public API contract.


25. OCC Extension vs OCC AddOn

This is an important topic for experienced SAP Commerce developers.

Modern SAP Commerce provides OCC Extensions.

An OCC extension:

Depends on commercewebservices

and is automatically imported into the Commerce Web Services Spring context.

SAP explicitly documents that OCC extensions are not web extensions and don't require the traditional AddOn installation process.

Architecture:

commercewebservices
        ↑
        |
    customocc

Whereas the older AddOn approach had a different dependency/installation model.

For new development, you should understand the OCC Extension architecture and also recognize older AddOn-based implementations in legacy projects.


26. Why OCC Extensions Are Better for New Development

With an OCC extension:

customocc
   |
   ├── Controllers
   ├── Spring beans
   ├── DTOs
   └── Messages

There is no need to copy controller files into the web application.

SAP documents that Commerce Web Services imports OCC extension Spring configurations automatically using the OCC extension path convention.


27. Performance Considerations

When designing an OCC API, don't only focus on making the endpoint work.

You should also consider performance.

Avoid unnecessary database queries

Bad:

Product
 ↓
Price query
 ↓
Stock query
 ↓
Category query
 ↓
Image query
 ↓
Review query

for every request.

Instead, carefully design your service and converter/populator pipeline.

Avoid huge responses

Don't return:

100+ fields

when the client needs:

5 fields

SAP specifically highlights configurable populators as a way to avoid unnecessary conversion and reduce performance/bandwidth costs.


28. Pagination

Never return thousands of products in one API response.

Instead:

GET /occ/v2/electronics/products?
    pageSize=20&
    currentPage=0

Conceptually:

Page 0 → 20 products
Page 1 → next 20
Page 2 → next 20

Pagination is especially important for:

  • Product search
  • Orders
  • Customers
  • Categories
  • Saved carts

29. API Versioning

Imagine your API is:

/occ/v2/electronics/customproducts/P100

Later you make a breaking change.

You should think carefully about API compatibility rather than unexpectedly changing the existing response contract.

Versioning provides a way to evolve APIs safely.


30. Testing Strategy

For a production OCC API, don't test only through Postman.

You should have:

Unit tests

Test:

Facade
Service
Populator
Converter

Integration tests

Test:

DAO
FlexibleSearch
Spring configuration

OCC/API tests

Test:

HTTP request
Authentication
Response
HTTP status
Error response

31. Interview Question: Explain Your Custom OCC API

If an interviewer asks:

"Explain how you created a custom OCC API in SAP Commerce."

A strong answer would be:

"I created a dedicated OCC extension and added a Spring MVC controller. The controller accepts the REST request and delegates to a facade. The facade calls the service layer, which contains the business logic and uses a DAO for data retrieval. The DAO executes FlexibleSearch and returns the model. I then use a converter and populator to transform the model into a Data object, which is exposed through a WsDTO. Finally, OCC serializes the WsDTO into JSON."

That's a much stronger answer than:

"I created a controller and exposed a REST endpoint."


32. Interview Question: Why Facade Between Controller and Service?

Answer:

The facade provides a presentation-oriented API and hides underlying service complexity.

It also allows us to:

  • Coordinate multiple services
  • Convert models to Data objects
  • Prepare data for presentation
  • Keep controllers thin

33. Interview Question: Why Use Populators?

Answer:

Populators separate individual conversion responsibilities.

For example:

ProductBasicPopulator
ProductPricePopulator
ProductStockPopulator
ProductImagePopulator

They can be composed into a converter pipeline.

This improves modularity and makes it easier to add or remove data population logic. SAP's documentation specifically recommends keeping conversion logic in populators rather than calling populators directly from application code.


34. Interview Question: What Happens When OCC Receives a Request?

A good answer:

HTTP Request
     ↓
Spring MVC
     ↓
OCC Controller
     ↓
Facade
     ↓
Service
     ↓
DAO
     ↓
Database

Then:

Database
     ↓
Model
     ↓
Converter
     ↓
Data
     ↓
WsDTO
     ↓
HTTP Response

35. Complete Project Structure

A practical project could eventually look like:

customcore
│
├── dao
├── service
└── model


customfacades
│
├── facade
├── data
├── converter
└── populator


customocc
│
├── controllers
├── dto
└── resources
    └── occ
        └── v2
            └── customocc
                └── web
                    └── spring
                        └── customocc-web-spring.xml

This separation is cleaner for a real enterprise project.


36. Final Architecture

Remember this:

                    CLIENT
                      |
                      | HTTP
                      ↓
              ┌───────────────┐
              │ OCC Controller│
              └───────┬───────┘
                      |
                      ↓
              ┌───────────────┐
              │    Facade     │
              └───────┬───────┘
                      |
                      ↓
              ┌───────────────┐
              │    Service    │
              └───────┬───────┘
                      |
                      ↓
              ┌───────────────┐
              │      DAO      │
              └───────┬───────┘
                      |
                      ↓
                FlexibleSearch
                      |
                      ↓
                   Database
                      |
                      ↓
                ProductModel
                      |
                      ↓
                  Converter
                      |
                      ↓
                  Populator
                      |
                      ↓
                 ProductData
                      |
                      ↓
                  WsDTO
                      |
                      ↓
                    JSON
                      |
                      ↓
                   CLIENT

Conclusion

Creating a custom OCC API is one of the most useful skills for a SAP Commerce developer.

The most important concepts to remember are:

OCC Extension
      ↓
Controller
      ↓
Facade
      ↓
Service
      ↓
DAO
      ↓
FlexibleSearch
      ↓
Model
      ↓
Converter
      ↓
Populator
      ↓
Data
      ↓
WsDTO
      ↓
JSON

The key principle is separation of responsibilities.

The Controller handles HTTP.

The Facade handles presentation-oriented orchestration.

The Service handles business logic.

The DAO handles data access.

The Converter/Populator handles object transformation.

The WsDTO defines what the API exposes.

This architecture makes SAP Commerce applications easier to maintain, test, scale and extend.

No comments:

Post a Comment