Upgrade Guide
This page documents the required upgrade path for Artemis, what the database migration does on startup, and how to verify that it worked.
How the Schema Is Applied
Artemis keeps its schema in Liquibase changelogs, organised into four directories that every startup reads in order:
| Directory | Contents |
|---|---|
baseline/ | The whole schema as of the last consolidation, in a single file |
history/ | Every changelog that baseline already contains, grouped by generation (v10/, v11/, …) |
changelog/ | Migrations written since the last consolidation |
data/ | Seed data for test environments |
Which of these actually execute depends on the database Artemis finds:
- An empty database applies the baseline, which creates the schema in one step. The changelogs under
history/are recorded as applied without being executed, because the baseline already contains everything they describe. Anything underchangelog/then runs normally. - An existing database already has the changelogs under
history/recorded from when it ran them, so Liquibase skips them. The baseline marks itself as applied without executing, because the schema it would create is already there. Only the changelogs underchangelog/run.
Both routes end with the same schema, so it makes no difference to the running instance whether it was installed fresh or upgraded over several years. A check on every Artemis pull request applies both routes to an empty database and compares the results on MySQL and PostgreSQL.
Why Intermediate Versions Are Required
Consolidation adds a baseline alongside the changelogs it summarises; it never rewrites them. An upgrade therefore needs no intermediate version on account of consolidation alone.
An intermediate version is required when the chain of changelogs leading to your database's state is no longer complete in the release you are installing. Two things cause that:
- Releases up to 9.x reused changeset identifiers. Each consolidation replaced the contents of the initial schema file while keeping the same changeset identifiers, so a database that recorded the previous major version's initial schema no longer matched the file. Upgrading repaired this on the way past, but only from the one version it was written for. This is why upgrading to 10.0.0 still requires passing through 9.9.3.
- A retired generation. When upgrades from a generation are no longer supported, its directory under
history/is removed. A database that never ran those changelogs can then no longer be brought forward, and the release notes name the version to pass through.
From 10.0.0 onwards each baseline has its own file and its own changeset identifiers, so identifiers never collide again. A new required version appears only when a generation is retired, which is an explicit decision announced in the release notes rather than an automatic consequence of every major release.
Upgrade Path Table
The table below shows the required intermediate version for each major release. You must deploy and start the Required Version at least once before upgrading to the Target Version.
| Target Version | Required Intermediate Version | Valid Until |
|---|---|---|
| 6.0.0 | 5.12.9 | 7.0.0 |
| 7.0.0 | 6.9.6 | 8.0.0 |
| 8.0.0 | 7.10.5 | 9.0.0 |
| 9.0.0 | 8.8.6 | 10.0.0 |
| 10.0.0 | 9.9.3 | 11.0.0 |
How to Read This Table
- Target Version: The major version you want to upgrade to.
- Required Intermediate Version: The minimum version your database must have been running before upgrading to the target version. Deploy this version, start Artemis, wait for it to fully initialize, then upgrade to the target version.
- Valid Until: The migration path is removed at this version. For example, the 5.12.9 → 6.0.0 path is available until 7.0.0, after which upgrading from 5.x is no longer supported.
Upgrade Scenarios
Fresh Installation
A fresh installation (empty database) can always use the latest version directly. No intermediate versions are needed.
Upgrading Within a Major Version (e.g., 8.5.0 → 8.8.6)
Minor and patch version upgrades within the same major version require no special steps. Simply deploy the new version. All incremental Liquibase changelogs will be applied automatically.
Upgrading Across One Major Version (e.g., 9.9.3 → 10.0.0)
- Deploy and start the required intermediate version (e.g., 9.9.3) to ensure all incremental migrations are applied.
- Wait for Artemis to fully start (check logs for "Started ArtemisApp").
- Stop Artemis.
- Take a backup of the database. This is the point to take one: it is the last state the previous release can read.
- Deploy and start the new major version (e.g., 10.0.0).
- Artemis then:
- Reads the previous version from the
artemis_versiontable and compares it against the required version. - Marks the baseline as applied without executing it, because your database already has that schema.
- Applies the changelogs written since the last consolidation.
- Writes the new version to the
artemis_versiontable.
- Reads the previous version from the
Artemis is not deployed as a rolling update. Stop every instance, start the first one alone and let it finish the migration, then start the rest. See Multiple Artemis Instances for the full procedure.
Upgrading Across Multiple Major Versions (e.g., 6.5.0 → 10.0.0)
You must upgrade through each intermediate version sequentially:
- 6.5.0 → 6.9.6 (deploy 6.9.6, start, wait for full initialization)
- 6.9.6 → 7.10.5 (deploy 7.10.5, start, wait for full initialization)
- 7.10.5 → 8.8.6 (deploy 8.8.6, start, wait for full initialization)
- 8.8.6 → 9.9.3 (deploy 9.9.3, start, wait for full initialization)
- 9.9.3 → 10.0.0 (deploy 10.0.0, start, wait for full initialization)
Skipping a Required Version
If you attempt to start a major version without having deployed the required intermediate version, Artemis will:
- Log an error message: "Cannot start Artemis because the migration path was not followed. Please deploy and start the release X.Y.Z first, otherwise the migration will fail"
- Exit with code 15.
- No database changes will be made.
To resolve this, deploy the required intermediate version first, start Artemis, wait for it to fully initialize, stop it, and then deploy the desired major version.
Verifying a Migration
Artemis reports what it did with the schema in the startup log.
On an empty database, the folded history is recorded before the baseline runs:
Empty database: recording the folded changelog history from classpath:config/liquibase/history/master.xml without executing it
Recorded the folded changelog history
On an existing database, that line is absent and Liquibase reports only the changelogs that still had to run. In both cases the last migration step logs the version being stored:
Updating latest version to 10.0 in database
To confirm afterwards, check that the recorded version matches the release you deployed:
SELECT latest_version FROM artemis_version;
Liquibase records every changeset it applied or recorded in DATABASECHANGELOG. A row whose EXECTYPE is MARK_RAN was deliberately skipped, which on an upgraded database is the normal state of the baseline row. An empty DATABASECHANGELOG on an instance that has started successfully means Liquibase was disabled, usually through the no-liquibase profile.
If a Migration Fails
A failing changeset stops the startup and the instance does not serve requests. The log names the changeset that failed and the statement it was running.
The steps below are for a migration failing on an existing database. A fresh installation failing while it records the folded history is a different case and needs nothing from you: the log reads "Recording the folded changelog history left N changeset(s) unrecorded", Artemis discards what it had recorded, and the next start repeats the whole step from the empty database it began with. Only if that message is followed by one saying the partial history could not be discarded do you have to act — drop the database and start again, because a half-recorded history would otherwise make the next start run the folded changelogs as ordinary migrations.
- Do not start the remaining instances. Only the first instance migrates, so the others are still stopped at this point.
- Read the failing changeset identifier from the log. Every changeset before it is committed and recorded; the failing one is not recorded.
- Resolve the underlying cause. A migration that adds a constraint usually fails because existing data violates it, and the log names the table and column.
- Restart the instance. Liquibase resumes at the changeset that failed and does not repeat the ones already recorded.
Restore the backup taken in step 4 of the upgrade procedure if the cause cannot be resolved. Reverting to the previous release on its own is not enough: the schema does not revert with it, and the older release may not be able to read the migrated schema.
Version-Specific Migration Steps
Some releases change behavior in a way that requires a one-time action on existing data. These steps are additional to the upgrade scenarios above.
10.1: Installation Metadata in Production
Every production core node refuses to start until three settings describe who runs the installation, whether or not telemetry is enabled:
info.operatorName(INFO_OPERATORNAME): the organization operating Artemis.info.operatorAdminName(INFO_OPERATORADMINNAME): the administrator's name.info.universityName(INFO_UNIVERSITYNAME): the university, school, or institution using Artemis. This setting is new.
Empty values and template values such as Admin, Some Artemis Operator, Your University, or Anonymous University are rejected, and the error names each setting to correct. Development servers, test servers (info.testServer: true), and build agents start without them. Add all three to the configuration of every production core node before you upgrade:
- Ansible:
artemis_operator_name,artemis_operator_admin_name, andartemis_university_name. The collection has no defaults for them and stops the play on a core node where one is missing. Run the configuration update before the version update, because a version update does not rewrite the configuration. - Helm:
artemis.config.operator.name,artemis.config.operator.adminName, andartemis.config.operator.universityName. The chart does not render while one of them is empty. - Docker Compose and manual installations: the environment variables or
application-prod.yml.
The About page shows the values that are set. See Telemetry for the settings and for what the installation reports.
10.0: Connection Collation on MySQL
Searches that match names, logins or titles compare both sides in lower case, so they behave the same on MySQL and PostgreSQL. On MySQL, this requires the connection to use utf8mb4_unicode_ci, like the tables. A JDBC URL without connectionCollation=utf8mb4_unicode_ci connects with utf8mb4_0900_ai_ci, and these searches then fail with Illegal mix of collations. Add the parameter to the datasource URL of every node before you upgrade. See MySQL Character Set and Collation. PostgreSQL installations need no action.
9.9: Accounts Provisioned Through SAML2
The sign-in page asks for the identifier first and then decides, from the account's internal flag, whether to offer a password field or to send the user to the configured identity provider. Only an account marked as internal authenticates against a password stored in Artemis; every other account is sent to the identity provider.
An account is either internal or external, never both. An internal account authenticates against a password stored in Artemis; an external one authenticates against the identity provider that manages it. A SAML2 account belongs in the second group, alongside OIDC and LDAP.
Accounts that Artemis provisioned through SAML2 before version 9.9 are stored as internal accounts, so they are offered the password form instead of the identity provider. The password such an account holds is a random one that was never given to its owner, so the owner cannot sign in until the flag is cleared. Accounts provisioned from LDAP are not affected, because they have always been stored as externally managed.
Clearing the internal flag takes away everything that authenticates against a password stored in Artemis: the password form on the sign-in page, Change password, the password reset function, and Git over HTTPS with a password. VCS access tokens and SSH keys keep working, because neither is checked against the password, and they are what an externally managed account uses for Git.
Identifying the Affected Accounts
Artemis records a SAML2_ACCOUNT_CREATE audit event whenever it provisions an account on a first SAML2 login, and the event names the account by its login. Match on login in every query on this page. It is the column Artemis constrains to be unique, and it is what Artemis itself looks the account up by when an assertion arrives: the login is derived from saml2.username-pattern, lowercased, and compared against jhi_user.login.
On version 9.9 these events are stored in jhi_persistent_audit_event, so the affected accounts can be listed exactly:
SELECT u.login, u.email
FROM jhi_user u
WHERE u.is_internal = TRUE
AND u.login IN (SELECT d.event_data
FROM jhi_persistent_audit_evt_data d
JOIN jhi_persistent_audit_event e ON e.event_id = d.event_id
WHERE d.name = 'user' AND e.event_type = 'SAML2_ACCOUNT_CREATE');
From version 10.0 on, the same events live in security_audit_event, with their key-value data in security_audit_evt_data. Substitute both table names in every query below.
If the audit log does not reach back far enough, build the list from the identity provider instead, and still match on login: export the accounts the identity provider manages, apply your configured saml2.username-pattern to each one to get the login Artemis would have stored, lowercase the result, and match jhi_user.login against that list.
Clearing the Flag
For a few accounts, open Administration → User management, edit the user, and clear the Internal checkbox.
For many accounts, update jhi_user.is_internal directly. Confirm that the SELECT above returns what you expect and that you have a backup, then run the update in a transaction:
BEGIN;
UPDATE jhi_user SET is_internal = FALSE
WHERE is_internal = TRUE
AND login IN (SELECT d.event_data
FROM jhi_persistent_audit_evt_data d
JOIN jhi_persistent_audit_event e ON e.event_id = d.event_id
WHERE d.name = 'user' AND e.event_type = 'SAML2_ACCOUNT_CREATE');
COMMIT;
Keep the internal administrator configured in artemis.user-management.internal-admin.username internal, and keep every other account that does authenticate against a password stored in Artemis internal as well. Accounts that Artemis provisions after the upgrade are stored correctly.
Database Compatibility
Artemis supports both MySQL and PostgreSQL. The migration system works identically on both databases. The baseline is written in database-agnostic Liquibase types, in three changesets:
- One shared changeset for everything both databases express the same way.
- One for MySQL and H2, covering the columns those spell differently, such as
enumandvarbinary. - One for PostgreSQL, covering the same columns as
varchar,textandbytea.
Every Artemis pull request applies the baseline and the changelogs it summarises to empty MySQL and PostgreSQL databases, and on each of them compares the baseline against the history it folds. A baseline that has drifted from its changelogs on either database therefore cannot reach a release.
MySQL Character Set and Collation
The server gets the collation from the MySQL configuration, for example --character_set_server=utf8mb4 --collation-server=utf8mb4_unicode_ci, and a database created afterwards inherits it. The connection does not: MySQL Connector/J connects with utf8mb4_0900_ai_ci, the MySQL default for utf8mb4, even when the server is configured with utf8mb4_unicode_ci. Set the connection collation in the JDBC URL with connectionCollation=utf8mb4_unicode_ci:
spring:
datasource:
url: jdbc:mysql://<db-host>:3306/Artemis?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf8&connectionCollation=utf8mb4_unicode_ci&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC
With the environment variable SPRING_DATASOURCE_URL, append the same parameter. Without it, the searches that match names, logins or titles fail with an error like the following, for example when searching for conversation members or course members:
Illegal mix of collations (utf8mb4_unicode_ci,IMPLICIT), (utf8mb4_0900_ai_ci,IMPLICIT), (utf8mb4_0900_ai_ci,COERCIBLE) for operation 'like'
To check an installation, make sure the datasource URL of every node contains connectionCollation=utf8mb4_unicode_ci. Then check the defaults of the server and of the Artemis database, and the collation of its tables and text columns:
SHOW VARIABLES WHERE Variable_name IN ('character_set_server', 'collation_server');
SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = 'Artemis';
SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'Artemis' AND TABLE_COLLATION <> 'utf8mb4_unicode_ci';
SELECT TABLE_NAME, COLUMN_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'Artemis' AND COLLATION_NAME IS NOT NULL AND COLLATION_NAME <> 'utf8mb4_unicode_ci';
- The first two queries should return
utf8mb4andutf8mb4_unicode_ci. The server defaults matter even when the database is correct: withcreateDatabaseIfNotExist=true, a recreatedArtemisdatabase takes them over. - The other two should return no rows. A column can declare its own collation, so it can deviate even when its table does not. The Artemis schema declares no column collation, so a deviating column was changed outside of Artemis.
The server configuration only applies when a database is created, so changing it later leaves the default of an existing Artemis database as it was. If that default differs, change it with the following statement. It applies to the tables that later migrations create and does not convert existing tables:
ALTER DATABASE Artemis CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
PostgreSQL installations are not affected.
Technical Details
The migration path check is implemented in DatabaseMigration.java. It runs before Liquibase on every application startup and reads the artemis_version table to determine the previous version. The MigrationPath class defines version boundaries:
MigrationPath("9.9.3")
→ requiredVersion = 9.9.3
→ upgradeVersion = 10.0.0 (next major)
→ nextUpgradeVersion = 11.0.0 (major after that)
When the version being started falls in [upgradeVersion, nextUpgradeVersion) and the recorded previous version is older than requiredVersion, Artemis logs the error and exits with code 15 before Liquibase runs. A database with no recorded version is a fresh installation and is always accepted.
Applying the changelogs is the job of ArtemisSpringLiquibase.java. It recognises an empty database — no DATABASECHANGELOG table, or one with no rows — and records the folded history with Liquibase's changelog-sync before running the update, using the same contexts and labels as the update itself.
A database is empty only until Liquibase has written its first row, so the recording happens exactly once in the life of an installation. There is nothing to configure and nothing to run by hand.