Skip to main content
Version: Release SaaS

Migrate Jython Script to Python 3 Script (Container)

This guide helps Release administrators and plugin developers migrate automation from the Jython Script task to the Python 3 Script (Container) task.

The Jython Script task (xlrelease.ScriptTask) runs inline Python 2 automation inside a release: reading and updating releases, phases, tasks, and variables, calling the Release API, and making HTTP calls. The Python 3 Script (Container) task provides the same capabilities on a modern Python 3 runtime.

For most scripts the change is mechanical. The helper APIs and the context helpers keep the same names and camelCase methods as Jython. The main work is updating Python 2 syntax, replacing reserved variables with helper calls, swapping HttpRequest for requests, removing Java imports, and returning values through result, result_2, and result_3.

This page covers the common cases. For the complete reference, see the Jython to Python 3 migration guide on GitHub. A community helper tool can also automate parts of the conversion.

Why Migrate

The Jython Script task is not deprecated, and existing Jython scripts continue to work, so you can migrate at your own pace. Migrating is recommended because Jython is Python 2.7, which reached end of life in 2020. The Python 3 Script (Container) task runs on Python 3.12 in its own container through the Release Runner, so it can use standard Python packages (such as requests and pydantic) and returns strongly typed Pydantic models instead of Java proxies.

Prerequisites

Before you migrate a script, make sure you have:

  • The Python3 Script (Container) plugin available. For more information, see Python 3 Script (Container) task.
  • A configured Digital.ai Release Runner to run container tasks.
  • A run-as user set on the release or template. Every API and helper call runs as this user, so API calls fail if it is not set. Set the Run automated tasks as user property.

Architecture Differences

Where the script runs explains every compatibility difference in this guide.

AspectJython ScriptPython 3 Script (Container)
Task type idxlrelease.ScriptTaskcontainerPython.PythonTask
RuntimeJython (Python 2.7 on the JVM)Python 3.12
Execution locationIn-process, inside the Release serverSeparate container through the Release Runner
API accessIn-process Java objectsRelease REST API through the bundled client
IdentityThe Release server processThe release's run-as user
Java interopYes (java.*, javax.*)No
SandboxYes (restricted file, network, classes)No sandbox; an ephemeral container
Third-party packagesJVM and Java librariesPython packages baked into the image
Return valuesConfigured task output propertiesresult, result_2, result_3
print() outputTask logContainer log and a single task comment

In Jython, the API objects live in the server process. In the container, they are REST clients that call back as the run-as user. That difference is why directly bound objects (release, releaseVariables, and others) become helper-function calls.

Compatibility Overview

This table summarizes what changes for each capability when you move a script to the Python 3 Script (Container) task.

CapabilityJythonPython 3 ScriptChange required
Release helper APIsYesYes, same names and methodsNone
Domain model imports (com.xebialabs.xlrelease.domain.*)YesYes, same namespaceUsually none
Context helpers (getCurrentRelease(), ...)YesYesNone
Variable helpers (getReleaseVariable(), ...)YesYesNone
Reserved objects (release, phase, task)YesNoUse getCurrentRelease() and similar
Reserved dicts (releaseVariables, globalVariables, folderVariables)YesNoUse getter and setter helpers
HttpRequest / HttpResponseYesNoUse requests
Java imports (java.*, javax.*)YesNoUse Python equivalents
Python 2 syntaxYesNoUse Python 3 syntax
Custom task output propertiesYesThree fixed namesMap to result, result_2, result_3, or release variables

Migration Checklist

Work through this per script:

  1. Create a Python 3 Script (Container) task.
  2. Confirm the release has a run-as user with the required permissions.
  3. Convert Python 2 syntax to Python 3.
  4. Replace reserved variables with helper functions.
  5. Replace HttpRequest with requests.
  6. Remove java.* and javax.* imports.
  7. Verify every third-party import is available in the container image.
  8. Map old output properties to result, result_2, result_3, or release variables.
  9. Keep Release API calls as they are.
  10. Run end to end with a container runner attached, then review the task comment and log.

Both task types can coexist, so you can convert one task or template at a time and validate as you go.

Reserved Variables to Helper Functions

Jython binds several objects and dictionaries directly into the script namespace. The Python 3 Script does not bind these. It exposes helper functions that fetch the data over the API on demand. Resolution is lazy, so a script that never references a helper makes no API request.

Jython (reserved)Python 3 Script (helper)Notes
releasegetCurrentRelease()Returns a typed Release
phasegetCurrentPhase()Returns a typed Phase
taskgetCurrentTask()Returns a typed Task
(enclosing folder)getCurrentFolder()Returns a typed Folder
releaseVariables["x"]getReleaseVariable("x")Raises KeyError if missing
releaseVariables["x"] = vsetReleaseVariable("x", v)Creates the variable if absent
folderVariables["folder.x"]getFolderVariable("folder.x")folder. prefix required
globalVariables["global.x"]getGlobalVariable("global.x")global. prefix required

Jython:

# 'release' and 'releaseVariables' are bound into the script
print "Release: %s (%s)" % (release.title, release.status)
buildNumber = releaseVariables["buildNumber"]
releaseVariables["deployTarget"] = "production"

Python 3 Script:

release = getCurrentRelease()
print(f"Release: {release.title} ({release.status})")
buildNumber = getReleaseVariable("buildNumber")
setReleaseVariable("deployTarget", "production")

Release Helper APIs

Every com.xebialabs.xlrelease.api.v1 helper object from Jython is available in the Python 3 Script under the same name, with the same camelCase methods. Each is created lazily and shares one authenticated client, so unused APIs cost nothing.

# Identical in Jython and Python 3 Script
release = releaseApi.getRelease(getCurrentRelease().id)
task = taskApi.getTask(getCurrentTask().id)

The most common objects are releaseApi, phaseApi, taskApi, folderApi, templateApi, configurationApi, variableApi, searchApi, and settingsApi. For the full list of API objects and their methods, see Digital.ai Release Python API Client.

Jython's repositoryService (reading shared configurations and global variables) maps to configurationApi here, for example configurationApi.getGlobalVariables() and configurationApi.getConfiguration(...). There is no repositoryService object.

Domain classes share the com.xebialabs.xlrelease.domain.* namespace with the Java and Jython API, so imports port across unchanged:

from com.xebialabs.xlrelease.domain.release import Release
from com.xebialabs.xlrelease.domain.task import Task

Returned objects are Pydantic models. Read attributes, modify them, and pass them back to an update method.

Working with Variables

The variable helpers replace Jython's releaseVariables, folderVariables, and globalVariables dictionaries. A getter returns the typed value and raises KeyError if the variable does not exist. A setter updates the variable, or creates it when missing, inferring the type from the Python value.

Setters infer the Release variable type from the Python value:

Python valueRelease variable type
boolxlrelease.BooleanVariable
intxlrelease.IntegerVariable
datetime or datexlrelease.DateVariable
dictxlrelease.MapStringStringVariable
list, set, or tuplexlrelease.SetStringVariable
anything else (str, ...)xlrelease.StringVariable

Release variables use bare names. Folder variables require a folder. prefix, and global variables require a global. prefix. Omitting a required prefix raises ValueError.

from datetime import datetime, timezone

setReleaseVariable("deployTarget", "production") # String
setReleaseVariable("retryCount", 3) # Integer
setReleaseVariable("dryRun", True) # Boolean
setReleaseVariable("approvers", ["alice", "bob"]) # Set
setReleaseVariable("config", {"env": "prod"}) # Map
target = getReleaseVariable("deployTarget") # 'production'

setFolderVariable("folder.buildVersion", "1.2.3")
setGlobalVariable("global.maintenanceMode", False)
caution

A password-type variable never returns its stored secret. The Release REST API masks the value server-side, so the variable getters return the literal string ********. The resolved value map from releaseApi.getVariableValues(...) omits password variables entirely. There is no REST call that returns the plaintext.

HttpRequest to requests

Jython's HttpRequest and HttpResponse classes are not available. Use the bundled requests library, which ships in the image because it backs the Release API client.

Jythonrequests
HttpRequest(params)requests.Session() or per-call arguments
request.get(path)requests.get(url)
request.post(path, body, contentType=...)requests.post(url, json=...) or data=...
response.getStatus()response.status_code
response.getResponse()response.text or response.json()
response.isSuccessful()response.ok or response.raise_for_status()
import requests

response = requests.get(
"https://api.example.com/users",
auth=("user", "pass"),
headers={"Accept": "application/json"},
timeout=30,
)
response.raise_for_status() # turn 4xx and 5xx into a task error
data = response.json()

Always set timeout=. Without it, a slow or unreachable host hangs the task until the runner kills it. A Jython HttpRequest often pulled its URL and credentials from an HTTP Server shared configuration. The container cannot read those shared configurations directly, so pass the values your script needs as release variables or task inputs.

Python 2 to Python 3 Syntax

Jython is Python 2.7 and the container runs Python 3.12. Port the common Python 2 constructs:

ConcernJython (2.7)Python 3 Script
Printprint "Hi", nameprint("Hi", name)
Exceptionsexcept Exception, e:except Exception as e:
Dict iterationd.iteritems()d.items()
has_keyd.has_key("k")"k" in d
Integer division5 / 2 (is 2)5 // 2 (is 2; / is float division)
Unicodeunicode(x), basestringstr(x), str
Ranges and inputxrange(), raw_input()range(), input()
map, filter, zipreturn listsreturn iterators; wrap in list(...) if needed

Java Imports to Python

The container runs pure Python and cannot load Java classes. Remove every java.* and javax.* import and use a Python equivalent.

Java class or APIPython 3 replacement
java.util.Date, Calendardatetime.datetime, datetime.date
java.text.SimpleDateFormatdatetime.strftime and strptime
java.lang.System.getenvos.getenv
java.io.File, java.nio.file.Pathpathlib.Path, os.path
java.util.HashMap, ArrayListdict, list
java.util.UUIDuuid
java.security.MessageDigesthashlib
java.net.HttpURLConnectionrequests (bundled)
org.json, Jacksonjson (standard library)
from datetime import datetime, timezone
import os, pathlib

now = datetime.now(timezone.utc)
home = pathlib.Path(os.getenv("HOME", "/tmp"))

Output Properties and print

The Python 3 Script exposes exactly three outputs. Assign these variable names:

result = "first value" # output property 'result'
result_2 = 42 # output property 'result_2'
result_3 = {"key": "value"} # output property 'result_3'

For more than three return values, or for structured output, write to release variables with setReleaseVariable(...) so downstream tasks can read them.

Everything printed with print(...) is echoed to the container log and posted as a single task comment when the script ends, even on failure. The task comment is visible in the UI, so never print secrets or large payloads. A raised exception fails the task with exit code 1 and reports the exception type, message, and a traceback trimmed to your script's own lines.

Common Patterns

Read a variable, do work, write a variable:

# Jython
build = releaseVariables["buildNumber"]
releaseVariables["artifactPath"] = "/builds/%s/app.jar" % build

# Python 3 Script
build = getReleaseVariable("buildNumber")
setReleaseVariable("artifactPath", f"/builds/{build}/app.jar")

Drive the Release API (largely unchanged):

# Jython
for t in releaseApi.getActiveTasks(release.id):
print t.title

# Python 3 Script
release_id = getCurrentRelease().id
for t in releaseApi.getActiveTasks(release_id):
print(t.title)

Unsupported Features

These Jython capabilities have no equivalent and must be removed or redesigned:

RemovedReplacement
Bound objects release, phase, taskgetCurrentRelease(), getCurrentPhase(), getCurrentTask()
Bound dicts releaseVariables, globalVariables, folderVariablesGetter and setter helpers
HttpRequest / HttpResponserequests
Java interop, reflection, JVM internalsPython equivalents
Reading shared-configuration secrets in-processRelease variables or task inputs
Persistent local files or server file-system accessRelease variables, attachments, or an external store

The container is ephemeral and isolated from the server file system. Do not assume files written by one task are visible to another task or the server.

Troubleshooting

The following table lists common errors, their causes, and how to fix them.

SymptomCauseFix
Cannot connect to Release API without server URL, username, or passwordNo run-as user on the releaseSet a run-as user on the release or template
403 Forbidden from an API callRun-as user lacks a permissionGrant it, or run as a user who has it
KeyError: No variable named XReading a missing variableCheck the name; create it first or guard with try/except KeyError
ValueError: ... must include the 'folder.' prefixMissing variable prefixUse "folder.x" or "global.x"
NameError: name 'release' is not definedUsing a Jython reserved objectUse the helper function
ModuleNotFoundError: No module named 'xlrelease' or javaUsing HttpRequest or a Java importUse requests or a Python equivalent
Task hangs until the runner times outHTTP call with no timeoutAdd timeout= to every requests call

Jython to Python 3 Cheat Sheet

This table maps common Jython constructs to their Python 3 Script (Container) equivalents.

JythonPython 3 Script (Container)
xlrelease.ScriptTaskcontainerPython.PythonTask
release / phase / taskgetCurrentRelease() / getCurrentPhase() / getCurrentTask()
releaseVariables["x"]getReleaseVariable("x")
releaseVariables["x"] = vsetReleaseVariable("x", v)
folderVariables["folder.x"]getFolderVariable("folder.x")
globalVariables["global.x"]getGlobalVariable("global.x")
releaseApi, taskApi, phaseApi, ...same names, same methods
from xlrelease.HttpRequest import HttpRequestimport requests
response.getStatus()response.status_code
print xprint(x)
except Exception, e:except Exception as e:
from java.util import Datefrom datetime import datetime
(configured task outputs)result, result_2, result_3

Migration Helper Tool

The Jython to Python 3 Migrator is a community helper tool that automates parts of the conversion. It is not officially supported by Digital.ai. Review its output before running migrated scripts.

References