| name | cap-java |
| description | Use when implementing SAP CAP Java handlers with Spring Boot: EventHandler, @Before @On @After annotation, CqnService, PersistenceService, ApplicationService, CdsCreateEventContext, CdsReadEventContext, @ServiceName, @Autowired, CQL Select Insert Update Delete, CAP Java SDK cds4j, cds-framework-spring-boot, pom.xml Maven dependency, Java event handler implementation.
|
| metadata | {"version":"1.1.0","keywords":["CAP Java","Spring Boot","EventHandler","CqnService","@Before","@On","@After","@ServiceName","CdsReadEventContext","PersistenceService","cds4j","Maven","Java service handler"],"related":{"cds-modeling":"CDS entities and services that Java handlers operate on","service-handlers":"Node.js equivalent — same concepts, different syntax","security-auth":"@requires and @restrict still apply in CAP Java","btp-deployment":"Maven build and CF deployment for CAP Java apps","testing":"CAP Java uses JUnit 5 and Spring Boot Test"}} |
CAP Java — Best Practices
Primary reference: https://cap.cloud.sap/docs/java/
Event handlers: https://cap.cloud.sap/docs/java/event-handlers/
Application services: https://cap.cloud.sap/docs/java/cqn-services/application-services
Spring Boot integration: https://cap.cloud.sap/docs/java/spring-boot-integration
CAP Java uses Spring Boot as its application framework. The recommended build is cds-framework-spring-boot. We highly recommend configuring cds-framework-spring-boot as the application framework — it provides a lot of integration with CAP out of the box, as well as enhanced features such as dependency injection and auto configuration.
Version requirements (CAP Java 5.x / CDS 10)
| Component | Minimum | Recommended |
|---|
| JDK | 21 (JDK 17 dropped in CAP Java 5) | 25 (LTS) |
| Spring Boot | 4.0 | 4.1 (with Spring Security 7.0) |
| Maven | 3.9.14+ | Latest stable |
Maven — pom.xml essentials
<parent>
<groupId>com.sap.cds</groupId>
<artifactId>cds-services-bom</artifactId>
<version>use.latest.version</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-framework-spring-boot</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-adapter-odata-v4
runtime
com.sap.cds
cds-feature-hana
runtime
com.h2database
h2
runtime
com.sap.cds
cds-maven-plugin
Event Handler class structure
The @Component annotation instructs Spring Boot to create a bean instance from the class. The EventHandler marker interface is required for CAP to identify the class as an event handler class among all beans and scan it for event handler methods. The optional @ServiceName annotation specifies the default service which event handlers are registered on.
package com.example.handlers;
import org.springframework.stereotype.Component;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.ServiceName;
import com.sap.cds.services.handler.annotations.Before;
import com.sap.cds.services.handler.annotations.On;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.cds.CqnService;
import com.sap.cds.services.cds.CdsCreateEventContext;
import com.sap.cds.services.cds.CdsReadEventContext;
@Component
@ServiceName("OrderService")
public class OrderServiceHandler implements EventHandler {
@Autowired
private PersistenceService db;
@Before(event = CqnService.EVENT_CREATE, entity = Orders_.CDS_NAME)
public void beforeCreateOrder(List<Orders> orders) {
for (Orders order : orders) {
if (order.getTotalAmount() == null || order.getTotalAmount().signum() <= 0) {
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Total amount must be positive");
}
}
}
@On(event = CqnService.EVENT_READ, entity = Orders_.CDS_NAME)
public void onReadOrders(CdsReadEventContext context) {
context.getCqn();
db.run(query);
context.setResult(result);
context.setCompleted();
}
{
orders.forEach(order -> log.info(, order.getId()));
}
}
CQL queries in Java
CqnSelect select = Select.from(Orders_.class)
.columns(o -> o.id(), o -> o.status(), o -> o.totalAmount())
.where(o -> o.status().eq("Open"));
Result result = db.run(select);
List<Orders> orders = result.listOf(Orders.class);
Orders newOrder = Orders.create();
newOrder.setId(UUID.randomUUID().toString());
newOrder.setStatus("Open");
CqnInsert insert = Insert.into(Orders_.class).entry(newOrder);
db.run(insert);
CqnUpdate update = Update.entity(Orders_.class)
.data("status", "Approved")
.where(o -> o.id().eq(orderId));
db.run(update);
CqnDelete delete = Delete.from(Orders_.class)
.where(o -> o.id().eq(orderId));
db.run(delete);
Custom action handler
@On(event = "submitOrder", entity = Orders_.CDS_NAME)
public void onSubmitOrder(CdsActionEventContext context) {
String orderId = context.get("orderID");
db.run(Update.entity(Orders_.class)
.data("status", "Submitted")
.where(o -> o.id().eq(orderId)));
context.setResult("Order " + orderId + " submitted successfully");
context.setCompleted();
}
Injecting services
@Component
@ServiceName("OrderService")
public class OrderServiceHandler implements EventHandler {
@Autowired
private PersistenceService db;
@Autowired
@Qualifier("ExternalService")
private RemoteService externalService;
@Autowired
private ApplicationLifecycleService als;
}
Error handling
import com.sap.cds.services.ServiceException;
import com.sap.cds.services.ErrorStatuses;
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid input: {}", fieldValue);
throw new ServiceException(ErrorStatuses.NOT_FOUND, "Order not found: {}", orderId);
throw new ServiceException(ErrorStatuses.FORBIDDEN, "Not authorized");
throw new ServiceException(ErrorStatuses.CONFLICT, MessageKeys.DUPLICATE_ORDER);
application.yaml — profiles
spring:
config:
activate:
on-profile: default
datasource:
url: jdbc:h2:mem:testdb
---
spring:
config:
activate:
on-profile: cloud
cds:
datasource:
auto-config:
enabled: true
Common mistakes to avoid
-
❌ Implementing EventHandler without @Component — Spring won't create the bean
-
✅ Always annotate with both @Component and implements EventHandler
-
❌ Using @On when you only need validation — replaces default persistence behavior
-
✅ Use @Before for validation/enrichment, @After for side effects, @On only when replacing default behavior
-
❌ Running CQL queries without injecting PersistenceService — no database access
-
✅ Always @Autowired PersistenceService db for database operations
-
❌ Not calling context.setCompleted() in an @On handler — framework may run default behavior anyway
-
✅ Call context.setCompleted() at the end of every @On handler
-
❌ Using com.sap.cds as your package GroupId for custom plugins
-
✅ This namespace is reserved for SAP — use your own groupId
-
❌ Hardcoding entity names as strings: entity = "OrderService.Orders"
-
✅ Use generated constants: entity = Orders_.CDS_NAME — type-safe, refactor-proof
Migration from CAP Java 4 to 5 — OpenRewrite
CAP Java 5 provides OpenRewrite recipes to automate the bulk of required code changes:
mvn org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=com.sap.cds:cds-services-recipes:5.0.0 \
-Drewrite.activeRecipes=com.sap.cds.services.migrations.Cap_5.0
Built-in OData processing (CAP Java 5+)
CAP Java 5 replaced Apache Olingo (which was retired in 2025) with built-in OData processing. The internal modules repackaged/odata-v4-lib and repackaged/odata-v2-lib have been removed. If your code directly referenced Olingo classes, rewrite it using CAP Java native APIs.
Harmonized Search (CAP Java 5+)
Search now uses the SAP HANA search syntax by default (harmonized with CAP Node.js and RAP):
| Operator | SAP HANA syntax | OData syntax |
|---|
| AND | space | AND |
| OR | OR | OR |
| NOT | - | NOT |
Fuzzy search is enabled by default on SAP HANA. To switch to OData search syntax:
cds:
sql:
search:
syntax: ODATA
Faster Typed Data Access — generateClasses
Generate implementation classes (instead of dynamic proxies) for accessor interfaces — up to 10× faster typed access:
<plugin>
<groupId>com.sap.cds</groupId>
<artifactId>cds-maven-plugin</artifactId>
<executions>
<execution>
<id>cds.generate</id>
<goals><goal>generate</goal></goals>
<configuration>
<generateClasses>true</generateClasses>
</configuration>
</execution>
</executions>
</plugin>