MTA Descriptor Generation
Generate a Multi-Target Application deployment descriptor (mtad.yaml) for deploying Neo-migrated applications to SAP BTP Cloud Foundry.
Usage
Generate an mtad.yaml for my migrated application
Create MTA descriptor with XSUAA and HANA services
Overview
The MTA descriptor (mtad.yaml) defines:
- Application modules (Java app, approuter)
- Required Cloud Foundry services (XSUAA, HANA, Destination, etc.)
- Service bindings between modules and services
- Build and deployment parameters
This skill analyzes which migration skills were applied to your application and generates the appropriate mtad.yaml configuration.
Prerequisites
Working directory: This skill must run inside the
-cf-migrationcopy of your app, created byjakarta-java25-migrationorneo-to-cf-migration-orchestrator. If your current directory does not end in-cf-migration, switch to it before proceeding.
Before invoking this skill, ensure you have invoked all required migration skills:
- jakarta-java25-migration -
Use the jakarta-java25-migration skill(REQUIRED) - sdk-replacement -
Use the sdk-replacement skill(REQUIRED) - Any feature skills that apply to your application
Also required:
- Application successfully compiles:
mvn clean compile - Understanding of which CF services your application requires
Determine Required Services
Based on the migration skills you've applied, identify required CF services:
| Migration Skill Applied | Required CF Service | Service Type |
|---|---|---|
| authentication-xsuaa | XSUAA | org.cloudfoundry.managed-service |
| destinations | Destination | org.cloudfoundry.managed-service |
| connectivity-onpremise | Destination + Connectivity | org.cloudfoundry.managed-service |
| persistence-hana | HANA Schema | com.sap.xs.hana-schema |
| document-management-sdm | SDM | org.cloudfoundry.managed-service |
| mail-destinations | (uses Destination service) | - |
| keystore-credstore | Credential Store | org.cloudfoundry.managed-service |
| monitoring-logging | (built-in CF logging) | - |
| tomee-runtime | (requires TARGET_RUNTIME property) | - |
MTA Templates
Template Selection
Choose the appropriate base template:
| Scenario | Template | When to Use |
|---|---|---|
| Basic app without web authentication | mtad-base.yaml | Internal services, background jobs, APIs with technical auth |
| App with XSUAA and Approuter | mtad-with-approuter.yaml | User-facing web applications requiring authentication |
| TomEE application | Add TARGET_RUNTIME: tomee |
Apps using EJB or requiring TomEE runtime |
Precondition — pom.xml MUST configure maven-war-plugin with <warName>${project.artifactId}</warName>
This skill owns the WAR filename rule (next section), which only holds if
pom.xmlactually writes the WAR under<artifactId>.war. Maven's default WAR filename is<artifactId>-<version>.war, which does NOT match what this skill writes intomtad.yaml(target/<artifactId>.war) — the deploy then fails with:
Error retrieving MTA: Error building MTA Archive:file path .../target/<artifactId>.war not foundThe other migration skills (
sdk-replacement,tomee-runtime, …) are dependency-management skills and do not own build packaging. Verify and, if needed, add the<build>block below topom.xmlbefore emitting the descriptor.
Required <build> block in pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<warName>${project.artifactId}</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
Why
3.4.0specifically: the defaultmaven-war-plugin:2.2is incompatible with Java 25 and fails withCannot access defaults field of Properties. The Common Issues section below has the same fix.
Self-check before continuing — run this from the project root; it
must print exactly 1:
grep -A 10 'maven-war-plugin' pom.xml | grep -c '<warName>${project.artifactId}</warName>'
If the count is 0, the plugin is missing or <warName> is set to
something else (often the leftover <warName>ROOT</warName> from older
templates). Fix pom.xml before emitting mtad.yaml; the build will
otherwise succeed but the descriptor and the WAR will disagree at deploy
time.
If pom.xml legitimately needs a different <warName> (e.g. the project
must serve at / and uses <warName>ROOT</warName>), update the
descriptor's path: to match — the rule is "descriptor follows pom",
not "pom follows descriptor".
WAR filename rule — read it from pom.xml, write it literally
The templates below show path: target/<artifactId>.war as a placeholder.
Before emitting mtad.yaml, open pom.xml, read the <artifactId>
value, and substitute it literally — for a project whose <artifactId>
is connectivity, write path: target/connectivity.war.
Why this matters in concrete terms:
cf deployreadspath:as a literal filesystem path under the descriptor's directory. It does NOT evaluate Maven property expressions, so a descriptor that sayspath: target/${project.artifactId}.warfails at deploy time withError building MTA Archive: file path target/${project.artifactId}.war not found.maven-war-pluginin the shipped pom templates is configured with<warName>${project.artifactId}</warName>— Maven writes a WAR named after the artifactId (e.g.target/connectivity.war). The descriptor must match what Maven actually writes, not a placeholder string.
Consequence to communicate downstream: sap_java_buildpack_jakarta always serves the app at / (ROOT context). It additionally registers the app at /<warName>/ only if web.xml has a servlet mapped to url-pattern: /. Integration tests and approuter destinations should use / — not /<artifactId>/ — unless the app explicitly maps a default servlet.
Template: Basic Application (mtad-base.yaml)
For applications without user authentication:
_schema-version: "3.3"
ID: my-app
version: 1.0.0
parameters:
parameters:
enable-parallel-deployments: true
modules:
- name: my-app-backend
type: java.tomcat
path: target/<artifactId>.war # ← substitute the literal artifactId from pom.xml
parameters:
memory: 1024M
disk-quota: 1024M
buildpack: sap_java_buildpack_jakarta
properties:
# Pin SapMachine JRE 25 explicitly. Always set this — never rely on the buildpack's
# implicit JRE choice, and keep maven.compiler.target in pom.xml at the same major
# version. Use SAPMachineJRE (bundled, offline) — NOT SAPMachineJDK (heavyweight,
# online-only).
JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
JBP_CONFIG_SAP_MACHINE_JRE: "{ version: 25.+ }"
TARGET_RUNTIME: tomcat
build-parameters:
builder: maven
build-result: target/*.war
provides:
- name: backend-api
properties:
url: ${default-url}
requires:
# Add service bindings here based on needs
- name: my-app-hana # If using persistence-hana
- name: my-app-destination # If using destinations
resources:
# Add required services here
Template: Application with XSUAA (mtad-with-approuter.yaml)
For user-facing web applications:
_schema-version: "3.2"
ID: my-app
version: 0.0.1
parameters:
enable-parallel-deployments: true
modules:
# Approuter module for authentication
- name: my-app-approuter
type: nodejs
path: approuter
parameters:
memory: 256M
disk-quota: 256M
routes:
- route: '${protocol}://my-app.${default-domain}'
protocol: http1
properties:
XS_APP_LOG_LEVEL: debug
TENANT_HOST_PATTERN: '(.*).cfapps.sap.hana.ondemand.com'
CF_NODEJS_LOGGING_LEVEL: "info"
requires:
- name: my-app-xsuaa
- name: my-app-java-app
group: destinations
properties:
name: backend-app-destination
url: '~{neo-app-url}'
forwardAuthToken: true
# Backend Java application
- name: my-app-backend
type: java.tomcat
path: target/<artifactId>.war # ← substitute the literal artifactId from pom.xml
parameters:
memory: 1024M
disk-quota: 1024M
buildpack: sap_java_buildpack_jakarta
properties:
ENABLE_SECURITY_JAVA_API_V2: true
# Pin SapMachine JRE 25 explicitly. Always set this — never rely on the buildpack's
# implicit JRE choice, and keep maven.compiler.target in pom.xml at the same major
# version. Use SAPMachineJRE (bundled, offline) — NOT SAPMachineJDK (heavyweight,
# online-only).
JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
JBP_CONFIG_SAP_MACHINE_JRE: "{ version: 25.+ }"
TARGET_RUNTIME: tomcat
SET_LOGGING_LEVEL: 'ROOT: INFO'
provides:
- name: my-app-java-app
properties:
neo-app-url: '${default-url}'
requires:
- name: my-app-xsuaa
- name: my-app-destination
resources:
- name: my-app-xsuaa
type: org.cloudfoundry.managed-service
parameters:
service: xsuaa
service-plan: application
path: ./xs-security.json
- name: my-app-destination
type: org.cloudfoundry.managed-service
parameters:
service: destination
service-plan: lite
Critical: The Java backend
pathmust point to the actual WAR file Maven produces — read the<artifactId>frompom.xmland substitute it literally (e.g.path: target/connectivity.warfor a project whoseartifactIdisconnectivity). See the "WAR filename rule" callout above this section. Note:sap_java_buildpack_jakartaalways serves the app at/— the WAR filename does not affect the runtime context path.
Note on
type: nodejsvstype: approuter.nodejs: Usetype: nodejswhen deploying the approuter with your ownapprouter/directory andpackage.json(which is the standard pattern for migrated Neo applications). The typeapprouter.nodejsis for the "managed approuter" pattern which is used only in specific multi-tenant scenarios.
Service Configuration Examples
Add these resource definitions based on services your application needs:
XSUAA (Authentication)
resources:
- name: ${app-name}-xsuaa
type: org.cloudfoundry.managed-service
parameters:
service: xsuaa
service-plan: application
path: ./xs-security.json # Created by authentication-xsuaa skill
Module requires:
requires:
- name: ${app-name}-xsuaa
Destination Service
resources:
- name: ${app-name}-destination
type: org.cloudfoundry.managed-service
parameters:
service: destination
service-plan: lite
Module requires:
requires:
- name: ${app-name}-destination
Connectivity Service (On-Premise)
resources:
- name: ${app-name}-connectivity
type: org.cloudfoundry.managed-service
parameters:
service: connectivity
service-plan: lite
Module requires:
requires:
- name: ${app-name}-connectivity
- name: ${app-name}-destination # Connectivity requires Destination
HANA Schema
CRITICAL — do NOT add
service: hanaorservice-plan: hdi-sharedundertype: com.sap.xs.hana-schema.❌ Anti-pattern that breaks deploy:
- name: ${app-name}-hana type: com.sap.xs.hana-schema parameters: service: hana # ← WRONG for hana-schema service-plan: hdi-shared # ← WRONG for hana-schema
service:andservice-plan:are keys for the GENERICtype: org.cloudfoundry.managed-service(xsuaa, destination, credstore, sdm, …).com.sap.xs.hana-schemais a dedicated MTA resource type that already implies the broker, the service, and the plan. Setting them explicitly causes the controller to reject the create-service call:
CF-UnprocessableEntity(10008): Invalid service plan. ... Ensure that the service plan is visible in your current space ...
resources:
- name: ${app-name}-hana
type: com.sap.xs.hana-schema
NO
parameters:block on the hana-schema resource. Specifically, do NOT setschema-name:,service:, orservice-plan:here. MTA'scom.sap.xs.hana-schematype does not support any of these at the resource level — the deployer drops them withParameter(s) "{<name>-hana=[schema-name]}" are not supported in the specified scope, or referenced by any other entities. These parameters are not processed and will be lost. The deploy still completes, but the warning is noisy and signals a malformed descriptor. The reference scenarios use only the two-line form above.
Module requires:
requires:
- name: ${app-name}-hana
The bare - name: is enough — MTA binds the resource into VCAP_SERVICES
under the resource's name:, and Tomcat's context.xml (or
resources.xml for TomEE) looks up the DataSource by that same name via
service_name_for_DefaultDB. No ~{...} cross-references or
TARGET_CONTAINER parameters are needed for the standard persistence
path.
Document Management (SDM)
resources:
- name: ${app-name}-sdm
type: org.cloudfoundry.managed-service
parameters:
service: sdm
service-plan: standard
Module requires:
requires:
- name: ${app-name}-sdm
Credential Store
resources:
- name: ${app-name}-credstore
type: org.cloudfoundry.managed-service
parameters:
service: credstore
service-plan: standard
config:
encryption:
user_key_name: "credstore-key"
Module requires:
requires:
- name: ${app-name}-credstore
Generation Steps
1. Analyze Application Requirements
Identify which services your application needs:
# Check for XSUAA dependencies
grep -r "com.sap.cloud.security.xsuaa" pom.xml
# Check for destination dependencies
grep -r "com.sap.cloud.sdk.cloudplatform.connectivity" pom.xml
# Check for HANA dependencies
grep -r "javax.sql.DataSource\|jakarta.persistence" src/main/java/
# Check for SDM dependencies
grep -r "com.sap.ecm.api" src/main/java/
2. Select Base Template
Copy the appropriate template:
# For basic app
cp skills/mta-descriptor/assets/mtad-base.yaml mtad.yaml
# For app with authentication
cp skills/mta-descriptor/assets/mtad-with-approuter.yaml mtad.yaml
3. Customize Application Properties
Update the mtad.yaml with your application details:
ID: my-application-name # Unique identifier
version: 1.0.0 # Your app version
modules:
- name: my-app-backend # Your module name
path: target/<artifactId>.war # Substitute the literal artifactId from pom.xml
parameters:
memory: 1024M # Adjust based on needs
disk-quota: 1024M # Minimum 1GB — 512M causes deployment errors
WAR naming: The descriptor's
path:must match exactly what Maven writes totarget/. The pom templates shipmaven-war-pluginwith<warName>${project.artifactId}</warName>, so the WAR is named after the artifactId (e.g.target/connectivity.war). Openpom.xml, read the<artifactId>value, and write it literally intomtad.yaml— Maven property expressions like${project.artifactId}are NOT evaluated bycf deployand will fail with "file path … not found". Note:sap_java_buildpack_jakartaalways serves the app at/— use/in approuter targets and integration tests, not/<artifactId>/.
4. Add Required Services
Based on your analysis in step 1, add the necessary service resources and module requirements.
Example: App with XSUAA, Destination, and HANA:
modules:
- name: my-app-backend
# ... other properties ...
requires:
- name: my-app-xsuaa
- name: my-app-destination
- name: my-app-hana
resources:
- name: my-app-xsuaa
type: org.cloudfoundry.managed-service
parameters:
service: xsuaa
service-plan: application
path: ./xs-security.json
- name: my-app-destination
type: org.cloudfoundry.managed-service
parameters:
service: destination
service-plan: lite
- name: my-app-hana
type: com.sap.xs.hana-schema
5. Configure TomEE Runtime (if applicable)
If you used the tomee-runtime skill:
modules:
- name: my-app-backend
properties:
TARGET_RUNTIME: tomee # Change from 'tomcat' to 'tomee'
6. Validate the Descriptor
Check the syntax and structure:
# Validate YAML syntax
yamllint mtad.yaml
# Or use basic validation
python3 -c "import yaml; yaml.safe_load(open('mtad.yaml'))"
Build and Deploy
After generating the mtad.yaml:
Deploy to Cloud Foundry
Primary flow — cf deploy . (recommended)
# Deploy directly from current directory
# cf deploy reads mtad.yaml and builds the MTA archive in-process
cf deploy . -f
This is the correct flow for migrated Neo applications. This skill generates
mtad.yaml(a deployment descriptor), whichcf deploy .reads directly. All 8 reference scenarios use this command.
CI/CD / archive flow — mbt build (requires extra step)
If you need a versioned .mtar archive (e.g. for CI/CD pipelines or distributing to other teams), you must first manually create an mta.yaml source descriptor — mbt build requires it and will fail without it. This skill does not generate mta.yaml.
# 1. Manually create mta.yaml (not generated by this skill)
# 2. Build the MTA archive
mbt build
# This creates: mta_archives/<app>_<version>.mtar
# 3. Deploy the archive
cf deploy mta_archives/*.mtar -f
mta.yamlvsmtad.yaml:mta.yamlis a source descriptor read bymbt buildto compile and package the app.mtad.yamlis a deployment descriptor read directly bycf deploy. They serve different purposes — having one does not substitute for the other.
Verify Deployment
# Check application status
cf apps
# Check service bindings
cf services
# View recent logs
cf logs my-app-backend --recent
Common Configurations
Multi-Module Application
modules:
- name: my-app-ui
type: html5
path: ui-module
- name: my-app-backend
type: java
path: backend-module
provides:
- name: backend-api
properties:
url: ${default-url}
- name: my-app-worker
type: java
path: worker-module
requires:
- name: backend-api
Environment Variables
modules:
- name: my-app-backend
properties:
MY_CUSTOM_VAR: "production-value"
SPRING_PROFILES_ACTIVE: "cloud"
JBP_CONFIG_RESOURCE_CONFIGURATION: "[tomcat/webapps/ROOT/WEB-INF/web.xml: {'web-app/session-config/session-timeout': 60}]"
Health Check Configuration
modules:
- name: my-app-backend
parameters:
health-check-type: http
health-check-http-endpoint: /actuator/health
health-check-timeout: 180
Troubleshooting
Issue: Service binding fails
Symptom: Application fails to bind to services during deployment
Solution: Verify service names in requires match service names in resources:
# Resource name
resources:
- name: my-app-xsuaa # This name...
# Must match in module requires
modules:
- name: my-app-backend
requires:
- name: my-app-xsuaa # ...must match exactly here
Issue: Module fails to build
Symptom: Build errors during cf deploy
Solution: Verify build parameters:
modules:
- name: my-app-backend
build-parameters:
builder: maven # Or 'npm', 'custom'
build-result: target/*.war # Verify path is correct
Issue: Memory errors
Symptom: Application crashes with out-of-memory errors
Solution: Increase memory allocation:
modules:
- name: my-app-backend
parameters:
memory: 2048M # Increase from 1024M
disk-quota: 1024M # Never use 512M — minimum is 1024M for Java buildpack
Issue: cf deploy fails immediately with Error building MTA Archive: file path … not found
Symptom: cf deploy exits non-zero within seconds, before any service is touched, with:
FAILEDError retrieving MTA: Error building MTA Archive:file path <project>/cf-ai-migrated/target/<X>.war not found
The Maven build step earlier in the pipeline reported BUILD SUCCESS. The build log line [INFO] Building war: …/target/<Y>.war shows a different filename than the descriptor declares.
Cause: mtad.yaml's path: doesn't match what Maven actually writes. Two flavors:
- The descriptor literally contains
path: target/${project.artifactId}.war— Maven property expressions are NOT evaluated bycf deploy, so the literal string${project.artifactId}is looked up on disk and not found. - The descriptor is left over from an older template that hard-coded
target/ROOT.war, but the pom is configured with<warName>${project.artifactId}</warName>(the current default in this skill's templates) and writestarget/<artifactId>.war.
Solution: Open pom.xml, read the <artifactId> value, and substitute it literally into mtad.yaml's path:. For a project whose <artifactId> is connectivity, the descriptor must say path: target/connectivity.war — not target/${project.artifactId}.war, not target/ROOT.war. After fixing the descriptor, re-run cf deploy (no rebuild needed).
Note:
sap_java_buildpack_jakartaalways serves the app at/(ROOT context) — the WAR filename does not affect the runtime context path. Integration tests and approuter destinations should use/, not/<artifactId>/.
Issue: 404 Not Found at runtime — app deployed but routes return 404
Symptom: Deploy succeeded; the app starts; every request to /<something> returns 404. Logs show Tomcat started and the WAR loaded.
Cause: sap_java_buildpack_jakarta deploys the WAR as ROOT context — the app is served at /, not at /<artifactId>. The 404 is likely caused by a missing servlet mapping in web.xml, a wrong security constraint, or approuter routes using /<artifactId>/ as a target prefix.
Solution: Check servlet mappings in web.xml. If using an approuter, verify that route targets do NOT include /<artifactId>/ as a prefix — the app serves at / directly. Run cf logs <app> --recent to see which paths Tomcat is actually receiving.
Issue: Routes quota exceeded
Symptom: cf deploy fails with Routes quota exceeded for organization '<org>'. quota: SUBSCRIPTION_QUOTA — total memory: 0, routes: 0
Cause: The CF org uses SUBSCRIPTION_QUOTA which has 0 routes and 0 memory by default. This is set by SAP's entitlement system.
Solution: A Global Account Administrator must assign CF Runtime memory in BTP Cockpit:
- BTP Cockpit → Global Account → Entitlements → Entity Assignments
- Select the subaccount → Configure Entitlements → Cloud Foundry Runtime → assign memory (e.g. 3GB)
- BTP automatically creates a new org quota named
<guid>-t-<subaccount-guid>and applies it
Note: cf create-space-quota and cf create-quota do NOT fix this — org-level route limits cannot be overridden by space quotas, and creating new org quotas requires CF admin rights.
Issue: maven-war-plugin fails with Cannot access defaults field of Properties
Symptom: Maven build fails with this error when using Java 25.
Cause: Default maven-war-plugin:2.2 is incompatible with Java 25.
Solution: Explicitly pin maven-war-plugin to 3.4.0 in pom.xml.
Integration with CF Plugin
After generating the mtad.yaml, use the cf-plugin skills for deployment:
# Use cf-plugin to deploy the MTA
/sap-btp-cf:cf-push-app my-app
# View deployment logs
/sap-btp-cf:cf-get-app-logs my-app
Post-Deployment Configuration
Assign Role Collections (XSUAA)
If using authentication-xsuaa:
- Open SAP BTP Cockpit
- Navigate to Security → Role Collections
- Assign role collection to users/user groups
- Verify by logging into the application
Create Destinations (Destination Service)
If using destinations skill:
- Open SAP BTP Cockpit
- Navigate to Connectivity → Destinations
- Create destinations as defined in the destinations skill
- Test connectivity from the application
Configure Cloud Connector (Connectivity)
If using connectivity-onpremise:
- Install and configure Cloud Connector
- Add virtual hosts matching destination configuration
- Map to on-premise systems
- Test connectivity
See Also
- authentication-xsuaa/SKILL.md - For xs-security.json and xs-app.json
- destinations/SKILL.md - For destination configuration
- connectivity-onpremise/SKILL.md - For Cloud Connector setup
- persistence-hana/SKILL.md - For HANA configuration
- tomee-runtime/SKILL.md - For TomEE-specific configuration