Skip to content

CAP Java: Migration Guide 3 to 4 #1824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions guides/security/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,13 +516,6 @@ Supported features are:
* Value references to constants, [user attributes](#user-attrs), and entity data (elements including [paths](#association-paths))
* [Exists predicate](#exists-predicate) based on subselects.


<div class="impl java">

CAP Java offers the option to enable rejection conditions for `UPDATE`, `DELETE` and custom events. Enable it using the configuration option <Config java keyOnly label="reject-selected-unauthorized-entity">cds.security.authorization.instance-based.reject-selected-unauthorized-entity.enabled: true</Config>.

</div>

::: info Avoid enumerable keys
In case the filter condition is not met in an `UPDATE` or `DELETE` request, the runtime rejects the request (response code 403) even if the user is not even allowed to read the entity. To avoid to disclosure the existence of such entities to unauthorized users, make sure that the key is not efficiently enumerable.
:::
Expand Down
2 changes: 1 addition & 1 deletion java/cqn-services/persistence-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ Advise the CDS Compiler to generate _tables without associations_, as associatio

#### SQL Optimization Mode

By default, the SAP HANA adapter in CAP Java generates SQL that is optimized for the new [HEX engine](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-performance-guide-for-developers/query-execution-engine-overview) in SAP HANA Cloud. To generate SQL that is compatible with SAP HANA 2.x ([HANA Service](https://help.sap.com/docs/HANA_SERVICE_CF/6a504812672d48ba865f4f4b268a881e/08c6e596b53843ad97ae68c2d2c237bc.html)) and [SAP HANA Cloud](https://www.sap.com/products/technology-platform/hana.html), set the [CDS property](../developing-applications/properties#cds-properties):
By default, the SAP HANA adapter in CAP Java generates SQL that is optimized for the new [HEX engine](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-performance-guide-for-developers/query-execution-engine-overview) in SAP HANA Cloud. To generate SQL that is compatible with SAP HANA 2.x ([HANA Service](https://help.sap.com/docs/HANA_SERVICE_CF/6a504812672d48ba865f4f4b268a881e/08c6e596b53843ad97ae68c2d2c237bc.html)) and [SAP HANA Cloud](https://www.sap.com/products/technology-platform/hana.html), set the (deprecated) [CDS property](../developing-applications/properties#cds-properties):

```yaml
cds.sql.hana.optimizationMode: legacy
Expand Down
3 changes: 2 additions & 1 deletion java/event-handlers/indicating-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ The OData adapter turns this exception into an OData error response to indicate

`Messages.throwIfError()` is automatically called at the end of the `Before` handler phase to abort the event processing in case of errors. It is recommended to use the Messages API for validation errors and rely on the framework calling `Messages.throwIfError()` automatically, instead of throwing a `ServiceException`.


## Formatting and Localization

Texts passed to both `ServiceException` and the `Messages` API can be formatted and localized.
Expand Down Expand Up @@ -126,7 +127,7 @@ throw new ServiceException(ErrorStatuses.BAD_REQUEST, "my.message.key", paramNum
CAP Java provides out-of-the-box translation for error messages that originate from input validation annotations such as `@assert...` or `@mandatory` and security annotations `@requires` and `@restrict`.

The error messages are optimized for UI scenarios and avoid any technical references to entity names or element names. Message targets are used where appropriate to allow the UI to show the error message next to the affected UI element.
You can enable these translated error messages by setting [<Config java>cds.errors.defaultTranslations.enabled: true</Config>](../developing-applications/properties#cds-errors-defaultTranslations-enabled).
You can disable these translated error messages by setting [<Config java>cds.errors.defaultTranslations.enabled: false</Config>](../developing-applications/properties#cds-errors-defaultTranslations-enabled).

### Provide custom error messages

Expand Down
99 changes: 99 additions & 0 deletions java/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,105 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/

[[toc]]

## CAP Java 3.10 to CAP Java 4.0 { #three-to-four }

### New License

The license of CAP Java 4.0 has changed and it's released under the new [SAP Developer License Agreement for CAP](https://cap.cloud.sap/resources/license/developer-license-3_2_CAP.txt).

### Minimum Versions

CAP Java 4.0 increased some minimum required versions:

| Dependency | Minimum Version |
| --- | --- |
| @sap/cds-dk | ^8 |

CAP Java 4.0 no longer supports @sap/cds-dk ^7.

### Removed feature `cds-feature-event-hub`

The feature `cds-feature-event-hub` has been removed from CAP Java. The successor of this feature is the [CDS Plugin for SAP Cloud Application Event Hub](https://github.com/cap-java/cds-feature-event-hub). This new CAP Java Plugin is published independently of CAP Java and is open source. It however has the same Maven group ID and artifact ID as the previous CAP Java feature and starts versioning with `4.0.0`.

### Changes in goal `generate` of the `cds-maven-plugin`

1. Removed already deprecated properties:
- sharedInterfaces
- uniqueEventContexts

2. Deprecated properties and marked for removal:
- eventContext
- cqnService

3. Changed default value of properties:
- excludes: "localized.**" -> null

### Removed unstructured messages from MessagingService { #removed-unstructured }

The deprecated `cds.messaging.services.<key>.structured` property, which allowed to handle messages as (unstructured) plain strings has now been completely removed.
Messaging services always represent data in a structured way by using a `Map`. Headers are represented in a separate `Map`.

With this change the following deprecated methods have been removed:
- `void MessagingService.emit(String, String)`
- `String TopicMessageEventContext.getData()`

When publishing a unstructured String-based message, wrap the message into a Map:

```java
// instead of
messagingService.emit("myTopic", "unstructured message");
// use
messagingService.emit("myTopic", Map.of("message", "unstructured message")); // [!code focus]
```

In case you receive a unstructured String-based message from a message broker, it is also wrapped into a structured message with a `message` property:

```java
@On(event = "myTopic")
void handleMyTopic(TopicMessageEventContext context) {
// instead of
String message = context.getData();
// use
String message = (String) context.getDataMap().get("message"); // [!code focus]
}
```

This change has no effect on CDS modelled events, as these have always been structured.

### Adjusted Property Defaults

Some property defaults have been adjusted:

| Property | Old Value | New Value | Explanation |
| --- | --- | --- | --- |
| `cds.security.authorization.deep.enabled` | false | true | [Deep Authorization](./security#deep-auth) is now enabled by default. |
| `cds.security.authorization.instanceBased.rejectSelectedUnauthorizedEntity.enabled` | false | true | Requests that violate instance-based authorization conditions now fail with 403, instead of 404. |
| `cds.security.authorization.instanceBased.checkInputData.enabled` | false | true | [Authorization Checks On Input Data](./security#input-data-auth) are now enabled by default. |
| `cds.errors.defaultTranslations.enabled` | false | true | [Translations for Validation Error Messages](./event-handlers/indicating-errors#ootb-translated-messages) are now enabled by default. |

### Deprecated Properties

The following properties have been deprecated and might be removed in a future major version:

- `cds.errors.combined`
- `cds.sql.hana.optimizationMode`
- `cds.outbox.services.<key>.storeLastError.enabled`

The functionality provided by these properties is enabled by default and there is no reason to switch these off.

### Removed Properties

The following table gives an overview about the removed properties:

| Removed Property | Replacement / Explanation |
| --- | --- |
| `cds.messaging.services.`<br>`<key>.structured` | [Use structured messages.](#removed-unstructured) |
| `cds.security.authorization.`<br>`emptyAttributeValuesAreRestricted` | [Use conditions with explicit checks for empty attributes.](../guides/security/authorization#user-attrs) |
| `cds.odataV4.serializer.buffered` | OData V4 responses are now always streamed while serialized. |
| `cds.environment.k8s` | Use service bindings from SAP BTP Operator on Kyma. |
| `cds.multiTenancy.subscriptionManager.`<br>`clientCertificateHeader` | `cds.security.authentication.`<br>`clientCertificateHeader` |
| `cds.multiTenancy.security.`<br>`internalUserAccess.enabled` | `cds.security.authentication.`<br>`internalUserAccess.enabled` |

## CAP Java 2.10 to CAP Java 3.0 { #two-to-three }

### Minimum Versions
Expand Down
8 changes: 2 additions & 6 deletions java/outbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,18 @@ cds:
services:
DefaultOutboxOrdered:
maxAttempts: 10
storeLastError: true
# ordered: true
DefaultOutboxUnordered:
maxAttempts: 10
storeLastError: true
# ordered: false
```
:::
You have the following configuration options:
- `maxAttempts` (default `10`): The number of unsuccessful emits until the message is ignored. It still remains in the database table.
- `storeLastError` (default `true`): If this flag is enabled, the last error that occurred, when trying to emit the message
of an entry, is stored. The error is stored in the element `lastError` of the entity `cds.outbox.Messages`.
- `ordered` (default `true`): If this flag is enabled, the outbox instance processes the entries in the order they have been submitted to it. Otherwise, the outbox may process entries randomly and in parallel, by leveraging outbox processors running in multiple application instances. This option can't be changed for the default persistent outboxes.

The persistent outbox stores the last error that occurred, when trying to emit the message of an entry. The error is stored in the element `lastError` of the entity `cds.outbox.Messages`.

### Configuring Custom Outboxes { #custom-outboxes}

Custom persistent outboxes can be configured using the `cds.outbox.services` section, for example in the _application.yaml_:
Expand All @@ -94,10 +92,8 @@ cds:
services:
MyCustomOutbox:
maxAttempts: 5
storeLastError: false
MyOtherCustomOutbox:
maxAttempts: 10
storeLastError: true
```
:::
Afterward you can access the outbox instances from the service catalog:
Expand Down
73 changes: 70 additions & 3 deletions java/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ Choose an appropriate XSUAA service plan to fit the requirements. For instance,

#### Proof-Of-Possession for IAS { #proof-of-possession}

Proof-Of-Possession is a technique for additional security where a JWT token is **bound** to a particular OAuth client for which the token was issued. On BTP, Proof-Of-Possession is supported by IAS and can be used by a CAP Java application.
Proof-Of-Possession is a technique for additional security where a JWT token is **bound** to a particular OAuth client for which the token was issued. On BTP, Proof-Of-Possession is supported by IAS and can be used by a CAP Java application.

Typically, a caller of a CAP application provides a JWT token issued by IAS to authenticate a request. With Proof-Of-Possession in place, a mutual TLS (mTLS) tunnel is established between the caller and your CAP application in addition to the JWT token. Clients calling your CAP application need to send the certificate provided by their `identity` service instance in addition to the IAS token.
Typically, a caller of a CAP application provides a JWT token issued by IAS to authenticate a request. With Proof-Of-Possession in place, a mutual TLS (mTLS) tunnel is established between the caller and your CAP application in addition to the JWT token. Clients calling your CAP application need to send the certificate provided by their `identity` service instance in addition to the IAS token.

On Cloud Foundry, the CAP application needs to be exposed under an additional route which accepts client certificates and forwards them to the application as `X-Forwarded-Client-Cert` header (for example, the `.cert.cfapps.<landscape>` domain).

Expand Down Expand Up @@ -361,7 +361,74 @@ More specific access control is provided by the `@restrict` annotation, which al

Whereas role-based authorization applies to whole entities only, [Instance-Based Authorization](../guides/security/authorization#instance-based-auth) allows to add more specific conditions that apply on entity instance level and depend on the attributes that are assigned to the request user. A typical use case is to narrow down the set of visible entity instances depending on user properties (for example, `CountryCode` or `Department`). Instance-based authorization is also basis for [domain-driven authorizations](../guides/security/authorization#domain-driven-authorization) built on more complex model constraints.

<span id="declarative-auth"></span>
#### Deep Authorization { #deep-auth}

Queries to Application Services are not only authorized by the target entity which has a `@restrict` or `@requires` annotation, but also for all __associated entities__ that are used in the statement. __Compositions__ are neither checked nor extended with additional filters.

For instance, consider the following model:

```cds
@(restrict: [{ grant: 'READ', to: 'Manager' }])
entity Books {...}

@(restrict: [{ grant: 'READ', to: 'Manager' }])
entity Orders {
key ID: String;
items: Composition of many {
key book: Association to Books;
quantity: Integer;
}
}
```

For the following OData request `GET Orders(ID='1')/items?$expand=book`, authorizations for `Orders` and for `Books` are checked.
If the entity `Books` has a `where` clause for [instance-based authorization](/java/security#instance-based-auth),
it will be added as a filter to the sub-request with the expand.

Custom CQL statements submitted to the [Application Service](/java/cqn-services/application-services) instances
are also authorized by the same rules including the path expressions and subqueries used in them.

For example, the following statement checks role-based authorizations for both `Orders` and `Books`,
because the association to `Books` is used in the select list.

```java
Select.from(Orders_.class,
f -> f.filter(o -> o.ID().eq("1")).items())
.columns(c -> c.book().title());
```

For modification statements with associated entities used in infix filters or where clauses,
role-based authorizations are checked as well. Associated entities require `READ` authorization, in contrast to the target of the statement itself.

The following statement requires `UPDATE` authorization on `Orders` and `READ` authorization on `Books`
because an association from `Orders.items` to the book is used in the where condition.

```java
Update.entity(Orders_.class, f -> f.filter(o -> o.ID().eq("1")).items())
.data("quantity", 2)
.where(t -> t.book().ID().eq(1));
```
:::tip Modification of Statements
Be careful when you modify or extend the statements in custom handlers.
Make sure you keep the filters for authorization.
:::

Deep authorizations are on by default and can be disabled by setting `cds.security.authorization.deep.enabled: false`.

#### Authorization Checks On Input Data { #input-data-auth }

Input data of `CREATE` and `UPDATE` events is also validated with regards to instance-based authorization conditions.
Invalid input that does not meet the condition is rejected with response code `400`.

Let's assume an entity `Orders` which restricts access to users classified by assigned accounting areas:

```cds
annotate Orders with @(restrict: [
{ grant: '*', where: 'accountingArea = $user.accountingAreas' } ]);
```

A user with accounting areas `[Development, Research]` is not able to send an `UPDATE` request, that changes `accountingArea` from `Research` or `Development` to `CarFleet`, for example.
Note that the `UPDATE` on instances _not matching the request user's accounting areas_ (for example, `CarFleet`) are rejected by standard instance-based authorization checks.

#### Current Limitations

Expand Down