From 8f9c4b70d7f1fbb6753bcb2ebc1bf98e9666f858 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 4 Feb 2026 17:28:21 +0200 Subject: [PATCH 1/8] Migrate to Java 25 Co-Authored-By: Claude --- .github/workflows/license-header-format.yml | 2 +- .../install/centos/instructions.md | 10 +- .../install/ubuntu/instructions.md | 10 +- .../script/api/tbel/TbDateTest.java | 134 ++++++++++++++---- .../server/dao/sql/alarm/AlarmRepository.java | 36 ++--- .../dao/sql/audit/AuditLogRepository.java | 12 +- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- packaging/java/build.gradle | 4 +- packaging/java/scripts/windows/install.bat | 8 +- packaging/js/build.gradle | 2 +- pom.xml | 20 ++- tbel-date-note.md | 81 +++++++++++ 13 files changed, 241 insertions(+), 82 deletions(-) create mode 100644 tbel-date-note.md diff --git a/.github/workflows/license-header-format.yml b/.github/workflows/license-header-format.yml index 0ac41c7bc8..6be1171b32 100644 --- a/.github/workflows/license-header-format.yml +++ b/.github/workflows/license-header-format.yml @@ -35,7 +35,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'corretto' # https://github.com/actions/setup-java?tab=readme-ov-file#supported-distributions - java-version: '21' + java-version: '25' cache: 'maven' # https://github.com/actions/setup-java?tab=readme-ov-file#caching-sbt-dependencies - name: License header format diff --git a/application/src/main/data/json/edge/instructions/install/centos/instructions.md b/application/src/main/data/json/edge/instructions/install/centos/instructions.md index 1822add6d8..be0a4903a4 100644 --- a/application/src/main/data/json/edge/instructions/install/centos/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/centos/instructions.md @@ -8,15 +8,15 @@ sudo yum install -y nano wget && sudo yum install -y https://dl.fedoraproject.or {:copy-code} ``` -#### Step 1. Install Java 17 (OpenJDK) -ThingsBoard service is running on Java 17. To install OpenJDK 17, follow these instructions: +#### Step 1. Install Java 25 (OpenJDK) +ThingsBoard service is running on Java 25. To install OpenJDK 25, follow these instructions: ```bash -sudo dnf install java-17-openjdk +sudo dnf install java-25-openjdk {:copy-code} ``` -Configure your operating system to use OpenJDK 17 by default. You can configure the default version by running the following command: +Configure your operating system to use OpenJDK 25 by default. You can configure the default version by running the following command: ```bash sudo update-alternatives --config java @@ -33,7 +33,7 @@ java -version The expected result is: ```text -openjdk version "17.x.xx" +openjdk version "25.x.xx" OpenJDK Runtime Environment (...) OpenJDK 64-Bit Server VM (build ...) ``` diff --git a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md index b4782dd46a..f0e7ac6a92 100644 --- a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md @@ -1,14 +1,14 @@ Here is the list of commands that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the server. -#### Step 1. Install Java 17 (OpenJDK) -ThingsBoard service is running on Java 17. To install OpenJDK 17, follow these instructions: +#### Step 1. Install Java 25 (OpenJDK) +ThingsBoard service is running on Java 25. To install OpenJDK 25, follow these instructions: ```bash -sudo apt update && sudo apt install openjdk-17-jdk +sudo apt update && sudo apt install openjdk-25-jdk {:copy-code} ``` -Configure your operating system to use OpenJDK 17 by default. You can configure the default version by running the following command: +Configure your operating system to use OpenJDK 25 by default. You can configure the default version by running the following command: ```bash sudo update-alternatives --config java @@ -25,7 +25,7 @@ java -version The expected result is: ```text -openjdk version "17.x.xx" +openjdk version "25.x.xx" OpenJDK Runtime Environment (...) OpenJDK 64-Bit Server VM (build ...) ``` diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java index 3529c94ee4..494eb64cde 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java @@ -209,7 +209,9 @@ class TbDateTest { .put("timeZone", "America/New_York") .put("dateStyle", "full") .toString())); - Assertions.assertEquals("середа, 6 вересня 2023 р.", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode() + // Java 21+ uses narrow no-break space (U+202F) before "р." per CLDR 42+ + String expectedDate_ukUA = Runtime.version().feature() >= 21 ? "середа, 6 вересня 2023\u202Fр." : "середа, 6 вересня 2023 р."; + Assertions.assertEquals(expectedDate_ukUA, d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("dateStyle", "full") .toString())); @@ -244,12 +246,18 @@ class TbDateTest { Assertions.assertNotNull(d.toLocaleTimeString()); Assertions.assertNotNull(d.toLocaleTimeString("en-US")); - Assertions.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", "America/New_York")); + // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+ + String expectedTime_enUS = Runtime.version().feature() >= 21 ? "9:04:05\u202FPM" : "9:04:05 PM"; + Assertions.assertEquals(expectedTime_enUS, d.toLocaleTimeString("en-US", "America/New_York")); Assertions.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York")); Assertions.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev")); Assertions.assertEquals("9:04:05 م", d.toLocaleTimeString( "ar-EG", "America/New_York")); - Assertions.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() + // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+ + String expectedFullTime_enUS = Runtime.version().feature() >= 21 + ? "9:04:05\u202FPM Eastern Daylight Time" + : "9:04:05 PM Eastern Daylight Time"; + Assertions.assertEquals(expectedFullTime_enUS, d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("timeStyle", "full") .toString())); @@ -292,14 +300,27 @@ class TbDateTest { Assertions.assertNotNull(d.toLocaleString()); Assertions.assertNotNull(d.toLocaleString("en-US")); - Assertions.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); + // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+ + String expectedLocale_enUS = Runtime.version().feature() >= 21 ? "9/5/23, 9:04:05\u202FPM" : "9/5/23, 9:04:05 PM"; + Assertions.assertEquals(expectedLocale_enUS, d.toLocaleString("en-US", "America/New_York")); Assertions.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); Assertions.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); - String expected_ver = Runtime.version().feature() == 11 ? "5\u200F/9\u200F/2023 9:04:05 م" : - "5\u200F/9\u200F/2023, 9:04:05 م"; - Assertions.assertEquals(expected_ver, d.toLocaleString( "ar-EG", "America/New_York")); + // Java 11 has no comma, Java 17 uses ASCII comma, Java 21+ uses Arabic comma (U+060C) + String expected_ar; + if (Runtime.version().feature() == 11) { + expected_ar = "5\u200F/9\u200F/2023 9:04:05 م"; + } else if (Runtime.version().feature() >= 21) { + expected_ar = "5\u200F/9\u200F/2023\u060C 9:04:05 م"; + } else { + expected_ar = "5\u200F/9\u200F/2023, 9:04:05 م"; + } + Assertions.assertEquals(expected_ar, d.toLocaleString( "ar-EG", "America/New_York")); - Assertions.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode() + // Java 21+ changed "at" to "," and uses NNBSP before AM/PM per CLDR 42+ + String expected_enUS_full = Runtime.version().feature() >= 21 + ? "Tuesday, September 5, 2023, 9:04:05\u202FPM Eastern Daylight Time" + : "Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time"; + Assertions.assertEquals(expected_enUS_full, d.toLocaleString("en-US", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") @@ -309,15 +330,26 @@ class TbDateTest { .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - Assertions.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() + // Java 21+ changed Ukrainian locale: "о" replaced with "," and NNBSP before "р." per CLDR 42+ + String expected_ukUA_full_summer = Runtime.version().feature() >= 21 + ? "середа, 6 вересня 2023\u202Fр., 04:04:05 за східноєвропейським літнім часом" + : "середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом"; + Assertions.assertEquals(expected_ukUA_full_summer, d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() .put("timeZone", "Europe/Kiev") .put("dateStyle", "full") .put("timeStyle", "full") .toString())); - expected_ver = Runtime.version().feature() == 11 ? "الثلاثاء، 5 سبتمبر 2023 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية" : - "الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية"; - Assertions.assertEquals(expected_ver, d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() + // Java 11 has no connector, Java 17 uses "في", Java 21+ uses Arabic comma "،" + String expected_ar_full; + if (Runtime.version().feature() == 11) { + expected_ar_full = "الثلاثاء، 5 سبتمبر 2023 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية"; + } else if (Runtime.version().feature() >= 21) { + expected_ar_full = "الثلاثاء، 5 سبتمبر 2023\u060C 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية"; + } else { + expected_ar_full = "الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية"; + } + Assertions.assertEquals(expected_ar_full, d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() .put("timeZone", "America/New_York") .put("dateStyle", "full") .put("timeStyle", "full") @@ -825,16 +857,44 @@ class TbDateTest { @Test public void toStringAsJs() { TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-04:00"); - Assertions.assertEquals("четвер, 1 січня 1976 р. о 06:15:30 за східноєвропейським стандартним часом", d1.toString("uk-UA", "Europe/Kyiv")); - Assertions.assertEquals("Thursday, January 1, 1976 at 6:15:30 AM Eastern European Standard Time", d1.toString("en-US", "Europe/Kyiv")); - Assertions.assertEquals("1976 Jan 1, Thu 06:15:30 Eastern European Time", d1.toString("UTC", "Europe/Kyiv")); - Assertions.assertEquals("Wednesday, December 31, 1975 at 10:15:30 PM Eastern Standard Time", d1.toString("en-US", "America/New_York")); - Assertions.assertEquals("1975 Dec 31, Wed 22:15:30 Eastern Standard Time", d1.toString("GMT", "America/New_York")); - Assertions.assertEquals("1975 Dec 31, Wed 22:15:30 Eastern Standard Time", d1.toString("UTC", "America/New_York")); + // Java 21+ changed Ukrainian locale: "о" (at) replaced with "," and NNBSP before "р." per CLDR 42+ + String expected_ukUA_full = Runtime.version().feature() >= 21 + ? "четвер, 1 січня 1976\u202Fр., 06:15:30 за східноєвропейським стандартним часом" + : "четвер, 1 січня 1976 р. о 06:15:30 за східноєвропейським стандартним часом"; + Assertions.assertEquals(expected_ukUA_full, d1.toString("uk-UA", "Europe/Kyiv")); + // Java 21+ changed "at" to "," and uses NNBSP before AM/PM per CLDR 42+ + String expected_enUS_Kyiv = Runtime.version().feature() >= 21 + ? "Thursday, January 1, 1976, 6:15:30\u202FAM Eastern European Standard Time" + : "Thursday, January 1, 1976 at 6:15:30 AM Eastern European Standard Time"; + Assertions.assertEquals(expected_enUS_Kyiv, d1.toString("en-US", "Europe/Kyiv")); + // Java 21+ changed timezone display for UTC locale + String expected_UTC_Kyiv = Runtime.version().feature() >= 21 + ? "1976 Jan 1, Thu 06:15:30 Kyiv (+0)" + : "1976 Jan 1, Thu 06:15:30 Eastern European Time"; + Assertions.assertEquals(expected_UTC_Kyiv, d1.toString("UTC", "Europe/Kyiv")); + // Java 21+ changed "at" to "," and uses NNBSP before AM/PM per CLDR 42+ + String expected_enUS_NY = Runtime.version().feature() >= 21 + ? "Wednesday, December 31, 1975, 10:15:30\u202FPM Eastern Standard Time" + : "Wednesday, December 31, 1975 at 10:15:30 PM Eastern Standard Time"; + Assertions.assertEquals(expected_enUS_NY, d1.toString("en-US", "America/New_York")); + // Java 21+ changed timezone display for GMT/UTC locales + String expected_GMT_NY = Runtime.version().feature() >= 21 + ? "1975 Dec 31, Wed 22:15:30 New York (+0)" + : "1975 Dec 31, Wed 22:15:30 Eastern Standard Time"; + Assertions.assertEquals(expected_GMT_NY, d1.toString("GMT", "America/New_York")); + Assertions.assertEquals(expected_GMT_NY, d1.toString("UTC", "America/New_York")); Assertions.assertEquals(d1.toUTCString("UTC"), d1.toUTCString()); - Assertions.assertEquals("четвер, 1 січня 1976 р., 03:15:30", d1.toUTCString("uk-UA")); - Assertions.assertEquals("Thursday, January 1, 1976, 3:15:30 AM", d1.toUTCString("en-US")); + // Java 21+ uses NNBSP before "р." per CLDR 42+ + String expected_ukUA_utc = Runtime.version().feature() >= 21 + ? "четвер, 1 січня 1976\u202Fр., 03:15:30" + : "четвер, 1 січня 1976 р., 03:15:30"; + Assertions.assertEquals(expected_ukUA_utc, d1.toUTCString("uk-UA")); + // Java 21+ uses NNBSP before AM/PM per CLDR 42+ + String expected_enUS_utc = Runtime.version().feature() >= 21 + ? "Thursday, January 1, 1976, 3:15:30\u202FAM" + : "Thursday, January 1, 1976, 3:15:30 AM"; + Assertions.assertEquals(expected_enUS_utc, d1.toUTCString("en-US")); Assertions.assertEquals("1976-01-01T03:15:30.567Z", d1.toJSON()); Assertions.assertEquals("1976-01-01T03:15:30.567Z", d1.toISOString()); @@ -842,22 +902,44 @@ class TbDateTest { Assertions.assertEquals("1976-01-01 06:15:30", d1.toLocaleString("UTC", "Europe/Kyiv")); Assertions.assertEquals("01.01.76, 06:15:30", d1.toLocaleString("uk-UA", "Europe/Kyiv")); Assertions.assertEquals("1975-12-31 22:15:30", d1.toLocaleString("UTC", "America/New_York")); - Assertions.assertEquals("12/31/75, 10:15:30 PM", d1.toLocaleString("en-US", "America/New_York")); + // Java 21+ uses NNBSP before AM/PM per CLDR 42+ + String expected_enUS_locale_NY = Runtime.version().feature() >= 21 + ? "12/31/75, 10:15:30\u202FPM" + : "12/31/75, 10:15:30 PM"; + Assertions.assertEquals(expected_enUS_locale_NY, d1.toLocaleString("en-US", "America/New_York")); Assertions.assertEquals("1976 Jan 1, Thu", d1.toDateString("UTC", "Europe/Kyiv")); - Assertions.assertEquals("четвер, 1 січня 1976 р.", d1.toDateString("uk-UA", "Europe/Kyiv")); + // Java 21+ uses NNBSP before "р." per CLDR 42+ + String expected_ukUA_date = Runtime.version().feature() >= 21 + ? "четвер, 1 січня 1976\u202Fр." + : "четвер, 1 січня 1976 р."; + Assertions.assertEquals(expected_ukUA_date, d1.toDateString("uk-UA", "Europe/Kyiv")); Assertions.assertEquals("1975 Dec 31, Wed", d1.toDateString("UTC", "America/New_York")); Assertions.assertEquals("Wednesday, December 31, 1975", d1.toDateString("en-US", "America/New_York")); Assertions.assertEquals("06:15:30", d1.toLocaleTimeString("uk-UA", "Europe/Kyiv")); Assertions.assertEquals("06:15:30", d1.toLocaleTimeString("UTC", "Europe/Kyiv")); - Assertions.assertEquals("10:15:30 PM", d1.toLocaleTimeString("en-US", "America/New_York")); + // Java 21+ uses NNBSP before AM/PM per CLDR 42+ + String expected_enUS_time = Runtime.version().feature() >= 21 ? "10:15:30\u202FPM" : "10:15:30 PM"; + Assertions.assertEquals(expected_enUS_time, d1.toLocaleTimeString("en-US", "America/New_York")); Assertions.assertEquals("22:15:30", d1.toLocaleTimeString("UTC", "America/New_York")); Assertions.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv")); - Assertions.assertEquals("06:15:30 Eastern European Time", d1.toTimeString("UTC", "Europe/Kyiv")); - Assertions.assertEquals("10:15:30 PM Eastern Standard Time", d1.toTimeString("en-US", "America/New_York")); - Assertions.assertEquals("22:15:30 Eastern Standard Time", d1.toTimeString("UTC", "America/New_York")); + // Java 21+ changed timezone display for UTC locale + String expected_UTC_time_Kyiv = Runtime.version().feature() >= 21 + ? "06:15:30 Kyiv (+0)" + : "06:15:30 Eastern European Time"; + Assertions.assertEquals(expected_UTC_time_Kyiv, d1.toTimeString("UTC", "Europe/Kyiv")); + // Java 21+ uses NNBSP before AM/PM per CLDR 42+ + String expected_enUS_timeStr = Runtime.version().feature() >= 21 + ? "10:15:30\u202FPM Eastern Standard Time" + : "10:15:30 PM Eastern Standard Time"; + Assertions.assertEquals(expected_enUS_timeStr, d1.toTimeString("en-US", "America/New_York")); + // Java 21+ changed timezone display for UTC locale + String expected_UTC_time_NY = Runtime.version().feature() >= 21 + ? "22:15:30 New York (+0)" + : "22:15:30 Eastern Standard Time"; + Assertions.assertEquals(expected_UTC_time_NY, d1.toTimeString("UTC", "America/New_York")); } @Test diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index f812d13f65..67e71ebcdf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -98,10 +98,8 @@ public interface AlarmRepository extends JpaRepository { "AND ea.entityType = :affectedEntityType " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + - "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 - "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 -// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + -// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + + "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + @@ -119,10 +117,8 @@ public interface AlarmRepository extends JpaRepository { "AND ea.entityType = :affectedEntityType " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + - "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 - "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 -// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + -// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + + "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + @@ -183,10 +179,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 - "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 -// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + -// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + + "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + @@ -199,10 +193,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 - "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 -// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + -// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + + "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + @@ -263,10 +255,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 - "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 -// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + -// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + + "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + @@ -280,10 +270,8 @@ public interface AlarmRepository extends JpaRepository { "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 - "AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 -// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + -// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + + "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " + + "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java index d685d9ee78..4266003706 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java @@ -33,8 +33,7 @@ public interface AuditLogRepository extends JpaRepository "a.tenantId = :tenantId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 -// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " + "AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " + @@ -54,8 +53,7 @@ public interface AuditLogRepository extends JpaRepository "AND a.entityType = :entityType AND a.entityId = :entityId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 -// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " + "AND (:textSearch IS NULL OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.actionType, CONCAT('%', :textSearch, '%')) = true " + @@ -75,8 +73,7 @@ public interface AuditLogRepository extends JpaRepository "AND a.customerId = :customerId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 -// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " + "AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " + @@ -96,8 +93,7 @@ public interface AuditLogRepository extends JpaRepository "AND a.userId = :userId " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + - "AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 -// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " + + "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " + "AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.actionType, CONCAT('%', :textSearch, '%')) = true " + diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 34a90e6fb7..74c33b2bfa 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bookworm-slim +FROM thingsboard/openjdk25:trixie-slim ENV PG_MAJOR=16 diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 0a0f61f904..b74727c166 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk17:bookworm-slim +FROM thingsboard/openjdk25:trixie-slim ENV PG_MAJOR 12 diff --git a/packaging/java/build.gradle b/packaging/java/build.gradle index 5ef4576a91..cdf96fcd26 100644 --- a/packaging/java/build.gradle +++ b/packaging/java/build.gradle @@ -16,7 +16,7 @@ import org.apache.tools.ant.filters.ReplaceTokens plugins { - id "nebula.ospackage" version "8.6.3" + id "com.netflix.nebula.ospackage" version "12.1.1" } buildDir = projectBuildDir @@ -92,7 +92,7 @@ buildRpm { archiveVersion = projectVersion.replace('-', '') archiveFileName = "${pkgName}.rpm" - // Support Java 17 (existing), plus Java 21 and Java 25 for RPM-based distros + // Support Java 25 (current), plus Java 21 and Java 17 (backward compatibility) for RPM-based distros // Keep using RPM boolean expression syntax since .or() chaining is for DEB only requires("(java-17 or java-17-headless or jre-17 or jre-17-headless or " + "java-21 or java-21-headless or jre-21 or jre-21-headless or " + diff --git a/packaging/java/scripts/windows/install.bat b/packaging/java/scripts/windows/install.bat index ca17408dfe..566286d4b5 100644 --- a/packaging/java/scripts/windows/install.bat +++ b/packaging/java/scripts/windows/install.bat @@ -7,11 +7,11 @@ setlocal ENABLEEXTENSIONS for /f tokens^=2-5^ delims^=.-_^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j%%k" @ECHO CurrentVersion %jver% -if %jver% NEQ 170 GOTO JAVA_NOT_INSTALLED +if %jver% NEQ 250 GOTO JAVA_NOT_INSTALLED :JAVA_INSTALLED -@ECHO Java 17 found! +@ECHO Java 25 found! @ECHO Installing thingsboard ... SET loadDemo=false @@ -50,8 +50,8 @@ POPD GOTO END :JAVA_NOT_INSTALLED -@ECHO Java 17 is not installed. Only Java 17 is supported -@ECHO Please go to https://adoptopenjdk.net/index.html and install Java 17. Then retry installation. +@ECHO Java 25 is not installed. Only Java 25 is supported +@ECHO Please go to https://adoptopenjdk.net/index.html and install Java 25. Then retry installation. PAUSE GOTO END diff --git a/packaging/js/build.gradle b/packaging/js/build.gradle index 15b8e546a1..001dafabb0 100644 --- a/packaging/js/build.gradle +++ b/packaging/js/build.gradle @@ -16,7 +16,7 @@ import org.apache.tools.ant.filters.ReplaceTokens plugins { - id "nebula.ospackage" version "8.6.3" + id "com.netflix.nebula.ospackage" version "12.1.1" } buildDir = projectBuildDir diff --git a/pom.xml b/pom.xml index c4cfec0ad3..a3498dd788 100755 --- a/pom.xml +++ b/pom.xml @@ -28,8 +28,8 @@ 2016 - 17 - 17 + 25 + 25 ${basedir} true none @@ -65,7 +65,7 @@ 3.25.5 1.76.0 1.2.8 - 1.18.38 + 1.18.40 1.2.5 1.2.5 1.7.1 @@ -111,6 +111,7 @@ 4.0.2 1.7.5 3.8.0 + 1.18.4 1.8.0-TB 2.38.0 1.24 @@ -605,7 +606,7 @@ maven-compiler-plugin 3.11.0 - 17 + 25 -Xlint:deprecation -Xlint:removal @@ -663,6 +664,7 @@ -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=200 + -XX:+EnableDynamicAgentLoading --add-opens=java.base/java.lang.reflect=ALL-UNNAMED -Dqueue.edqs.local.rocksdb_path="target/rocksdb/fork_${surefire.forkNumber}/edqs" -Dqueue.calculated_fields.rocks_db_path="target/rocksdb/fork_${surefire.forkNumber}/cf" @@ -913,6 +915,16 @@ pom import + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + + + net.bytebuddy + byte-buddy-agent + ${bytebuddy.version} + org.thingsboard.langchain4j langchain4j-bom diff --git a/tbel-date-note.md b/tbel-date-note.md new file mode 100644 index 0000000000..5c0f3ea741 --- /dev/null +++ b/tbel-date-note.md @@ -0,0 +1,81 @@ +## Release Note: TBEL Date Formatting Changes (Java 21+) + +### Summary + +When upgrading ThingsBoard to Java 21 or later, users may notice changes in locale-formatted date/time strings produced by the `TbDate` class in TBEL scripts. These changes are caused by updates to the Unicode CLDR (Common Locale Data Repository) in Java 21+. + +### What Changed + +| Format | Java 17 | Java 21+ | +|--------|---------|----------| +| Time with AM/PM | `9:04:05 PM` | `9:04:05 PM` (narrow no-break space before AM/PM) | +| Full datetime (English) | `Tuesday, September 5, 2023 at 9:04:05 PM` | `Tuesday, September 5, 2023, 9:04:05 PM` | +| Full datetime (Ukrainian) | `середа, 6 вересня 2023 р. о 04:04:05` | `середа, 6 вересня 2023 р., 04:04:05` | +| Short datetime (Arabic) | `5/9/2023, 9:04:05 م` | `5/9/2023، 9:04:05 م` (Arabic comma) | +| UTC timezone display | `Eastern European Time` | `Kyiv (+0)` | + +### Impact + +TBEL scripts that rely on exact string matching or parsing of locale-formatted dates may behave differently after upgrading. For example: + +```javascript +// ⚠️ May break after upgrade - string comparison +var dateStr = new Date(ts).toLocaleString("en-US", "America/New_York"); +if (dateStr == "9/5/23, 9:04:05 PM") { + // Will fail - invisible character difference +} + +// ⚠️ May break after upgrade - string splitting +var parts = new Date(ts).toLocaleTimeString("en-US", tz).split(" "); +// "PM" now preceded by narrow no-break space (U+202F), not regular space +``` + +### Recommendations + +1. **Use ISO formats for comparisons and storage** + ```javascript + // ✅ Stable across Java versions + var dateStr = new Date(ts).toISOString(); // "2023-09-05T21:04:05Z" + var dateJson = new Date(ts).toJSON(); // "2023-09-05T21:04:05.000Z" + ``` + +2. **Use explicit patterns for consistent formatting** + ```javascript + // ✅ Explicit pattern - stable output + var dateStr = new Date(ts).toLocaleString("en-US", '{"pattern": "M/d/yyyy, h:mm:ss a"}'); + ``` + +3. **Use numeric getters for comparisons** + ```javascript + // ✅ Compare numeric values instead of strings + var d = new Date(ts); + if (d.getHours() == 21 && d.getMinutes() == 4) { ... } + ``` + +4. **Avoid string equality checks on formatted dates** + ```javascript + // ❌ Avoid + if (date.toLocaleString() == storedDateString) { ... } + + // ✅ Prefer + if (date.getTime() == storedTimestamp) { ... } + ``` + +### Affected Methods + +The following `TbDate` methods may produce different output: +- `toLocaleString()` +- `toLocaleDateString()` +- `toLocaleTimeString()` +- `toString()` +- `toDateString()` +- `toTimeString()` +- `toUTCString()` + +### Unaffected Methods + +These methods produce consistent output across Java versions: +- `toISOString()` +- `toJSON()` +- `getTime()` / `valueOf()` +- All numeric getters (`getFullYear()`, `getMonth()`, `getDate()`, `getHours()`, etc.) From b1e75cdfa0972ca27c557896ac039a60950dcc81 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 5 Feb 2026 12:00:30 +0200 Subject: [PATCH 2/8] Fix Gradle builds --- packaging/java/build.gradle | 52 +++++++++++++++++++++++++------------ packaging/js/build.gradle | 37 ++++++++++++++++---------- pom.xml | 2 +- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/packaging/java/build.gradle b/packaging/java/build.gradle index cdf96fcd26..6bd28968e9 100644 --- a/packaging/java/build.gradle +++ b/packaging/java/build.gradle @@ -21,7 +21,9 @@ plugins { buildDir = projectBuildDir version = projectVersion -distsDirName = "./" +base { + distsDirectory = layout.buildDirectory.dir("./") +} // OS Package plugin configuration ospackage { @@ -33,8 +35,8 @@ ospackage { into pkgInstallFolder - user pkgUser - permissionGroup pkgUser + user = pkgUser + permissionGroup = pkgUser // Copy the actual .jar file from(mainJar) { @@ -42,19 +44,25 @@ ospackage { rename { String fileName -> "${pkgName}.jar" } - fileMode 0500 + filePermissions { + unix('r-x------') // 0500 + } into "bin" } if("${pkgCopyInstallScripts}".equalsIgnoreCase("true")) { // Copy the install files from("${buildDir}/bin/install/install.sh") { - fileMode 0775 + filePermissions { + unix('rwxrwxr-x') // 0775 + } into "bin/install" } from("${buildDir}/bin/install/upgrade.sh") { - fileMode 0775 + filePermissions { + unix('rwxrwxr-x') // 0775 + } into "bin/install" } @@ -67,14 +75,18 @@ ospackage { from("${buildDir}/conf") { exclude "${pkgName}.conf" fileType CONFIG | NOREPLACE - fileMode 0754 + filePermissions { + unix('rwxr-xr--') // 0754 + } into "conf" } // Copy the data files from("${buildDir}/data") { fileType CONFIG | NOREPLACE - fileMode 0754 + filePermissions { + unix('rwxr-xr--') // 0754 + } into "data" } @@ -102,7 +114,9 @@ buildRpm { include "${pkgName}.conf" filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm']) fileType CONFIG | NOREPLACE - fileMode 0754 + filePermissions { + unix('rwxr-xr--') // 0754 + } into "${pkgInstallFolder}/conf" } @@ -111,13 +125,15 @@ buildRpm { preUninstall file("${buildDir}/control/rpm/prerm") postUninstall file("${buildDir}/control/rpm/postrm") - user pkgUser - permissionGroup pkgUser + user = pkgUser + permissionGroup = pkgUser // Copy the system unit files from("${buildDir}/control/template.service") { addParentDirs = false - fileMode 0644 + filePermissions { + unix('rw-r--r--') // 0644 + } into "/usr/lib/systemd/system" rename { String filename -> "${pkgName}.service" @@ -143,7 +159,9 @@ buildDeb { include "${pkgName}.conf" filter(ReplaceTokens, tokens: ['pkg.platform': 'deb']) fileType CONFIG | NOREPLACE - fileMode 0754 + filePermissions { + unix('rwxr-xr--') // 0754 + } into "${pkgInstallFolder}/conf" } @@ -157,13 +175,15 @@ buildDeb { preUninstall file("${buildDir}/control/deb/prerm") postUninstall file("${buildDir}/control/deb/postrm") - user pkgUser - permissionGroup pkgUser + user = pkgUser + permissionGroup = pkgUser // Copy the system unit files from("${buildDir}/control/template.service") { addParentDirs = false - fileMode 0644 + filePermissions { + unix('rw-r--r--') // 0644 + } into "/lib/systemd/system" rename { String filename -> "${pkgName}.service" diff --git a/packaging/js/build.gradle b/packaging/js/build.gradle index 001dafabb0..13a2779f34 100644 --- a/packaging/js/build.gradle +++ b/packaging/js/build.gradle @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import org.apache.tools.ant.filters.ReplaceTokens plugins { id "com.netflix.nebula.ospackage" version "12.1.1" @@ -21,7 +20,9 @@ plugins { buildDir = projectBuildDir version = projectVersion -distsDirName = "./" +base { + distsDirectory = layout.buildDirectory.dir("./") +} // OS Package plugin configuration ospackage { @@ -33,18 +34,22 @@ ospackage { into pkgInstallFolder - user pkgUser - permissionGroup pkgUser + user = pkgUser + permissionGroup = pkgUser // Copy the executable file from("${buildDir}/package/linux/bin/${pkgName}") { - fileMode 0500 + filePermissions { + unix('r-x------') // 0500 + } into "bin" } // Copy the init file from("${buildDir}/package/linux/init/template") { - fileMode 0500 + filePermissions { + unix('r-x------') // 0500 + } into "init" rename { String filename -> "${pkgName}" @@ -54,7 +59,9 @@ ospackage { // Copy the config files from("${buildDir}/package/linux/conf") { fileType CONFIG | NOREPLACE - fileMode 0754 + filePermissions { + unix('rwxr-xr--') // 0754 + } into "conf" } @@ -78,13 +85,15 @@ buildRpm { preUninstall file("${buildDir}/control/rpm/prerm") postUninstall file("${buildDir}/control/rpm/postrm") - user pkgUser - permissionGroup pkgUser + user = pkgUser + permissionGroup = pkgUser // Copy the system unit files from("${buildDir}/control/template.service") { addParentDirs = false - fileMode 0644 + filePermissions { + unix('rw-r--r--') // 0644 + } into "/usr/lib/systemd/system" rename { String filename -> "${pkgName}.service" @@ -111,13 +120,15 @@ buildDeb { preUninstall file("${buildDir}/control/deb/prerm") postUninstall file("${buildDir}/control/deb/postrm") - user pkgUser - permissionGroup pkgUser + user = pkgUser + permissionGroup = pkgUser // Copy the system unit files from("${buildDir}/control/template.service") { addParentDirs = false - fileMode 0644 + filePermissions { + unix('rw-r--r--') // 0644 + } into "/lib/systemd/system" rename { String filename -> "${pkgName}.service" diff --git a/pom.xml b/pom.xml index a3498dd788..806659f567 100755 --- a/pom.xml +++ b/pom.xml @@ -650,7 +650,7 @@ org.thingsboard gradle-maven-plugin - 1.0.12 + 1.0.15 com.github.eirslett From c4ff463296f729d6b9a06a9e47cdaf504cec6919 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Fri, 6 Feb 2026 15:55:39 +0200 Subject: [PATCH 3/8] Add own pluginRepository for gradle-maven-plugin --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 806659f567..fb56c476ef 100755 --- a/pom.xml +++ b/pom.xml @@ -1967,4 +1967,10 @@ + + + thingsboard-repo + https://repo.thingsboard.io/artifactory/libs-release-public + + From 19b3c113f72c9cfbf46fe2bbeaa9ca08e2597a87 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Fri, 6 Feb 2026 16:28:14 +0200 Subject: [PATCH 4/8] Try fix Dockerfiles for tb-postgres and tb-cassandra --- msa/tb/docker-cassandra/Dockerfile | 11 +++++------ msa/tb/docker-postgres/Dockerfile | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 74c33b2bfa..358f2907c8 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -44,15 +44,14 @@ COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb. RUN apt-get update \ && apt-get install -y --no-install-recommends wget nmap procps gnupg2 \ - && echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \ - && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O- | apt-key add - \ - && echo "deb https://debian.cassandra.apache.org 40x main" | tee -a /etc/apt/sources.list.d/cassandra.sources.list > /dev/null \ - && wget -q https://downloads.apache.org/cassandra/KEYS -O- | apt-key add - \ + && mkdir -p /etc/apt/keyrings \ + && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O- | gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list > /dev/null \ + && wget -q https://downloads.apache.org/cassandra/KEYS -O- | gpg --dearmor -o /etc/apt/keyrings/cassandra.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/cassandra.gpg] https://debian.cassandra.apache.org 40x main" | tee /etc/apt/sources.list.d/cassandra.sources.list > /dev/null \ && apt-get update \ && apt-get install -y --no-install-recommends cassandra cassandra-tools postgresql-${PG_MAJOR} \ && rm -rf /var/lib/apt/lists/* \ - && update-rc.d cassandra disable \ - && update-rc.d postgresql disable \ && apt-get purge -y --auto-remove \ && sed -i.old '/ulimit/d' /etc/init.d/cassandra \ && chmod a+x /tmp/*.sh \ diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index b74727c166..6ce066d1ed 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -36,12 +36,12 @@ COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb. RUN apt-get update \ && apt-get install -y --no-install-recommends wget gnupg2 \ - && echo "deb http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null \ - && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O- | apt-key add - \ + && mkdir -p /etc/apt/keyrings \ + && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O- | gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(. /etc/os-release && echo -n $VERSION_CODENAME)-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list > /dev/null \ && apt-get update \ && apt-get install -y --no-install-recommends postgresql-${PG_MAJOR} \ && rm -rf /var/lib/apt/lists/* \ - && update-rc.d postgresql disable \ && apt-get purge -y --auto-remove \ && chmod a+x /tmp/*.sh \ && mv /tmp/start-tb.sh /usr/bin \ From 8414db39409f18859b8676080dda95aebbda304f Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Fri, 6 Feb 2026 16:36:28 +0200 Subject: [PATCH 5/8] Fix TbelInvokeDocsIoTest --- .../server/service/script/TbelInvokeDocsIoTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java index 67e4ba8f32..76caa4d53c 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java @@ -2355,7 +2355,8 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest { expected.put("date_1", d1.toString()); TbDate d2 = new TbDate(2023, 8, 6, 4, 4, 5, "Europe/Berlin"); expected.put("dLocal_2", d2.toLocaleString()); - expected.put("dLocal_2_us", "8/5/23, 10:04:05 PM"); + // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+ + expected.put("dLocal_2_us", Runtime.version().feature() >= 21 ? "8/5/23, 10:04:05\u202FPM" : "8/5/23, 10:04:05 PM"); expected.put("dIso_2", d2.toISOString()); expected.put("date_2", d2.toString()); Object actual = invokeScript(evalScript(decoderStr), msgStr); @@ -2387,7 +2388,8 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest { """, s2); LinkedHashMap expected = new LinkedHashMap<>(); TbDate d1 = new TbDate(2023, 8, 6, 4, 4, 5); - expected.put("dLocal_1_us", "8/6/23, 4:04:05 AM"); + // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+ + expected.put("dLocal_1_us", Runtime.version().feature() >= 21 ? "8/6/23, 4:04:05\u202FAM" : "8/6/23, 4:04:05 AM"); expected.put("dIso_1", d1.toISOString()); expected.put("d1", d1.toString()); TbDate d2 = new TbDate(s2); @@ -2419,7 +2421,8 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest { TbDate d = new TbDate(2023, 8, 6, 4, 4, 5, "Europe/Berlin"); expected.put("dIso", d.toISOString()); expected.put("dLocal_utc", d.toLocaleString("UTC")); - expected.put("dLocal_us", "8/5/23, 10:04:05 PM"); + // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+ + expected.put("dLocal_us", Runtime.version().feature() >= 21 ? "8/5/23, 10:04:05\u202FPM" : "8/5/23, 10:04:05 PM"); expected.put("dLocal_de", "06.08.23, 04:04:05"); Object actual = invokeScript(evalScript(decoderStr), msgStr); assertEquals(expected, actual); From 20b1b8f41f049a0ea71c4eb79c9277db1f9aabb9 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Fri, 6 Feb 2026 16:46:31 +0200 Subject: [PATCH 6/8] Update Edge installation instructions --- .../install/centos/instructions.md | 28 ++++++++++--------- .../install/ubuntu/instructions.md | 2 ++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/application/src/main/data/json/edge/instructions/install/centos/instructions.md b/application/src/main/data/json/edge/instructions/install/centos/instructions.md index be0a4903a4..964cdcb887 100644 --- a/application/src/main/data/json/edge/instructions/install/centos/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/centos/instructions.md @@ -1,10 +1,12 @@ -Here is the list of commands that can be used to quickly install ThingsBoard Edge on RHEL/CentOS 7/8 and connect to the server. +Here is the list of commands that can be used to quickly install ThingsBoard Edge on RHEL/CentOS 9/10 and connect to the server. + +**Note:** OpenJDK 25 requires RHEL/CentOS 9+ or RHEL/CentOS 10+. Earlier versions (RHEL/CentOS 7, 8) are not supported. #### Prerequisites Before continuing to installation, execute the following commands to install the necessary tools: ```bash -sudo yum install -y nano wget && sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm +sudo dnf install -y nano wget {:copy-code} ``` @@ -12,7 +14,7 @@ sudo yum install -y nano wget && sudo yum install -y https://dl.fedoraproject.or ThingsBoard service is running on Java 25. To install OpenJDK 25, follow these instructions: ```bash -sudo dnf install java-25-openjdk +sudo dnf install -y java-25-openjdk {:copy-code} ``` @@ -41,7 +43,7 @@ OpenJDK 64-Bit Server VM (build ...) #### Step 2. Configure ThingsBoard Edge Database ThingsBoard Edge supports **SQL** and **hybrid** database configurations. -In this guide, we’ll use an **SQL** database. +In this guide, we'll use an **SQL** database. For more details about the hybrid setup, please refer to the official installation instructions on the ThingsBoard documentation site. To install the PostgreSQL database, run these commands: @@ -54,19 +56,19 @@ sudo dnf update Install the repository RPM: -* **For CentOS/RHEL 8:** +* **For RHEL/CentOS 9:** ```bash -# Install the repository RPM (For CentOS/RHEL 8): -sudo sudo dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm +# Install the repository RPM (for RHEL/CentOS 9): +sudo dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm {:copy-code} ``` -* **For CentOS/RHEL 9:** +* **For RHEL/CentOS 10:** ```bash -# Install the repository RPM (for CentOS 9): -sudo dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm +# Install the repository RPM (for RHEL/CentOS 10): +sudo dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-10-x86_64/pgdg-redhat-repo-latest.noarch.rpm {:copy-code} ``` @@ -91,8 +93,8 @@ sudo -u postgres psql -c "\password" Then, enter and confirm the password. -Since ThingsBoard Edge uses the PostgreSQL database for local storage, configuring MD5 authentication ensures that only authenticated users or -applications can access the database, thus protecting your data. After configuring the password, +Since ThingsBoard Edge uses the PostgreSQL database for local storage, configuring MD5 authentication ensures that only authenticated users or +applications can access the database, thus protecting your data. After configuring the password, edit the pg_hba.conf file to use MD5 hashing for authentication instead of the default method (ident) for local IPv4 connections. To replace ident with md5, run the following command: @@ -102,7 +104,7 @@ sudo sed -i 's/^host\s\+all\s\+all\s\+127\.0\.0\.1\/32\s\+ident/host all {:copy-code} ``` -Then run the command that will restart the PostgreSQL service to apply configuration changes, connect to the database as a postgres user, +Then run the command that will restart the PostgreSQL service to apply configuration changes, connect to the database as a postgres user, and create the ThingsBoard Edge database (tb_edge). To connect to the PostgreSQL database, enter the PostgreSQL password. ```bash diff --git a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md index f0e7ac6a92..6f74c09c08 100644 --- a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md @@ -1,5 +1,7 @@ Here is the list of commands that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the server. +**Note:** OpenJDK 25 requires Ubuntu 22.04 LTS or newer. Earlier versions (20.04 and below) are not supported. + #### Step 1. Install Java 25 (OpenJDK) ThingsBoard service is running on Java 25. To install OpenJDK 25, follow these instructions: From 8aa564bcc4f318c813773cbd1dcf6f4d5f36b947 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 18 Feb 2026 14:45:42 +0200 Subject: [PATCH 7/8] Remove TBEL Java 25 tmp note --- tbel-date-note.md | 81 ----------------------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 tbel-date-note.md diff --git a/tbel-date-note.md b/tbel-date-note.md deleted file mode 100644 index 5c0f3ea741..0000000000 --- a/tbel-date-note.md +++ /dev/null @@ -1,81 +0,0 @@ -## Release Note: TBEL Date Formatting Changes (Java 21+) - -### Summary - -When upgrading ThingsBoard to Java 21 or later, users may notice changes in locale-formatted date/time strings produced by the `TbDate` class in TBEL scripts. These changes are caused by updates to the Unicode CLDR (Common Locale Data Repository) in Java 21+. - -### What Changed - -| Format | Java 17 | Java 21+ | -|--------|---------|----------| -| Time with AM/PM | `9:04:05 PM` | `9:04:05 PM` (narrow no-break space before AM/PM) | -| Full datetime (English) | `Tuesday, September 5, 2023 at 9:04:05 PM` | `Tuesday, September 5, 2023, 9:04:05 PM` | -| Full datetime (Ukrainian) | `середа, 6 вересня 2023 р. о 04:04:05` | `середа, 6 вересня 2023 р., 04:04:05` | -| Short datetime (Arabic) | `5/9/2023, 9:04:05 م` | `5/9/2023، 9:04:05 م` (Arabic comma) | -| UTC timezone display | `Eastern European Time` | `Kyiv (+0)` | - -### Impact - -TBEL scripts that rely on exact string matching or parsing of locale-formatted dates may behave differently after upgrading. For example: - -```javascript -// ⚠️ May break after upgrade - string comparison -var dateStr = new Date(ts).toLocaleString("en-US", "America/New_York"); -if (dateStr == "9/5/23, 9:04:05 PM") { - // Will fail - invisible character difference -} - -// ⚠️ May break after upgrade - string splitting -var parts = new Date(ts).toLocaleTimeString("en-US", tz).split(" "); -// "PM" now preceded by narrow no-break space (U+202F), not regular space -``` - -### Recommendations - -1. **Use ISO formats for comparisons and storage** - ```javascript - // ✅ Stable across Java versions - var dateStr = new Date(ts).toISOString(); // "2023-09-05T21:04:05Z" - var dateJson = new Date(ts).toJSON(); // "2023-09-05T21:04:05.000Z" - ``` - -2. **Use explicit patterns for consistent formatting** - ```javascript - // ✅ Explicit pattern - stable output - var dateStr = new Date(ts).toLocaleString("en-US", '{"pattern": "M/d/yyyy, h:mm:ss a"}'); - ``` - -3. **Use numeric getters for comparisons** - ```javascript - // ✅ Compare numeric values instead of strings - var d = new Date(ts); - if (d.getHours() == 21 && d.getMinutes() == 4) { ... } - ``` - -4. **Avoid string equality checks on formatted dates** - ```javascript - // ❌ Avoid - if (date.toLocaleString() == storedDateString) { ... } - - // ✅ Prefer - if (date.getTime() == storedTimestamp) { ... } - ``` - -### Affected Methods - -The following `TbDate` methods may produce different output: -- `toLocaleString()` -- `toLocaleDateString()` -- `toLocaleTimeString()` -- `toString()` -- `toDateString()` -- `toTimeString()` -- `toUTCString()` - -### Unaffected Methods - -These methods produce consistent output across Java versions: -- `toISOString()` -- `toJSON()` -- `getTime()` / `valueOf()` -- All numeric getters (`getFullYear()`, `getMonth()`, `getDate()`, `getHours()`, etc.) From bc196d61eb7cf79b2f52ddfcdbeb56c7821e2045 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 19 Feb 2026 15:37:34 +0200 Subject: [PATCH 8/8] Remove dead SecurityManager code (JEP 486) System.getSecurityManager() always returns null since Java 24. Co-Authored-By: Claude Opus 4.6 --- .../org/thingsboard/common/util/ThingsBoardThreadFactory.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java index a6218b2eb8..272e44ba7e 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java +++ b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java @@ -33,9 +33,7 @@ public class ThingsBoardThreadFactory implements ThreadFactory { } private ThingsBoardThreadFactory(String name) { - SecurityManager s = System.getSecurityManager(); - group = (s != null) ? s.getThreadGroup() : - Thread.currentThread().getThreadGroup(); + group = Thread.currentThread().getThreadGroup(); namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";