From c319a095fe0fb51297967cbc25bae8a7f9e4f115 Mon Sep 17 00:00:00 2001 From: pradeep85841 Date: Sat, 7 Mar 2026 20:59:55 +0530 Subject: [PATCH 1/7] Fixing Jakarta 3.1 TCK issue: Added strict check for Character type while keeping legacy support --- .../activemq/command/ActiveMQMessage.java | 12 ++-- .../activemq/ActiveMQMessagePropertyTest.java | 68 +++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java diff --git a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java index 2b9c86dc17b..d393600d1f6 100644 --- a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java +++ b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java @@ -525,17 +525,19 @@ public void setProperties(Map properties) throws JMSException { } protected void checkValidObject(Object value) throws MessageFormatException { - - boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long; - valid = valid || value instanceof Float || value instanceof Double || value instanceof Character || value instanceof String || value == null; + // Types allowed by the Jakarta Messaging Specification + boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || + value instanceof Integer || value instanceof Long || value instanceof Float || + value instanceof Double || value instanceof String || value == null; if (!valid) { ActiveMQConnection conn = getConnection(); // conn is null if we are in the broker rather than a JMS client if (conn == null || conn.isNestedMapAndListEnabled()) { - if (!(value instanceof Map || value instanceof List)) { - throw new MessageFormatException("Only objectified primitive objects, String, Map and List types are allowed but was: " + value + " type: " + value.getClass()); + // Standard rules: only primitives and Strings are allowed + if (!(value instanceof Map || value instanceof List || value instanceof Character)) { + throw new MessageFormatException("Only objectified primitive objects, String, Map, List and Character types are allowed but was: " + value + " type: " + value.getClass()); } } else { throw new MessageFormatException("Only objectified primitive objects and String types are allowed but was: " + value + " type: " + value.getClass()); diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java new file mode 100644 index 00000000000..675754b29c8 --- /dev/null +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java @@ -0,0 +1,68 @@ +package org.apache.activemq; + +import jakarta.jms.Connection; +import jakarta.jms.Message; +import jakarta.jms.MessageFormatException; +import jakarta.jms.Session; +import org.junit.Test; +import static org.junit.Assert.fail; + +public class ActiveMQMessagePropertyTest { + + @Test + public void testSetObjectPropertyCompliance() throws Exception { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false"); + + // Test 1: Strict Mode (TCK Scenario) + factory.setNestedMapAndListEnabled(false); + Connection strictConn = factory.createConnection(); + Session strictSession = strictConn.createSession(false, Session.AUTO_ACKNOWLEDGE); + Message strictMsg = strictSession.createMessage(); + + try { + strictMsg.setObjectProperty("charProp", 'A'); + fail("Strict mode (TCK) should reject Character type"); + } catch (MessageFormatException e) { + // Correct for Jakarta 3.1 + } + strictConn.close(); + + // Test 2: Legacy Mode (Backward Compatibility Scenario) + factory.setNestedMapAndListEnabled(true); + Connection legacyConn = factory.createConnection(); + Session legacySession = legacyConn.createSession(false, Session.AUTO_ACKNOWLEDGE); + Message legacyMsg = legacySession.createMessage(); + + try { + legacyMsg.setObjectProperty("charProp", 'A'); + // legacy support is preserved! + } catch (MessageFormatException e) { + fail("Legacy mode should still allow Character to avoid breaking existing users"); + } + legacyConn.close(); + } + + @Test + public void testLegacyValidationAllowsNonSpecTypes() throws Exception { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false"); + + // Default ActiveMQ legacy behavior + factory.setNestedMapAndListEnabled(true); + + Connection connection = factory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Message message = session.createMessage(); + + // Validates that Character/Map types are still accepted when legacy support is enabled. + try { + message.setObjectProperty("charProperty", 'A'); + message.setObjectProperty("mapProperty", new java.util.HashMap<>()); + } catch (MessageFormatException e) { + fail("Legacy mode should allow Character and Map types, but threw: " + e.getMessage()); + } + + connection.close(); + } + + +} From 8850abc80bac1055c69c4788112bfca3231775d9 Mon Sep 17 00:00:00 2001 From: pradeep85841 Date: Sat, 7 Mar 2026 21:56:17 +0530 Subject: [PATCH 2/7] Add missing ASF license header to test file --- .../activemq/ActiveMQMessagePropertyTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java index 675754b29c8..8ea8d15c4b2 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java @@ -1,3 +1,19 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.apache.activemq; import jakarta.jms.Connection; From e1b2debc4accbcb597e27a384bfc5bf7118cedd0 Mon Sep 17 00:00:00 2001 From: pradeep85841 Date: Sat, 7 Mar 2026 22:20:12 +0530 Subject: [PATCH 3/7] Set nestedMapAndListEnabled to false by default for Jakarta 3.1 compliance --- .../src/main/java/org/apache/activemq/ActiveMQConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java index b9f0eebf9ac..ac8092386a2 100644 --- a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java +++ b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java @@ -150,7 +150,7 @@ public class ActiveMQConnection implements Connection, TopicConnection, QueueCon private boolean optimizeAcknowledge; private long optimizeAcknowledgeTimeOut = 0; private long optimizedAckScheduledAckInterval = 0; - private boolean nestedMapAndListEnabled = true; + private boolean nestedMapAndListEnabled = false; private boolean useRetroactiveConsumer; private boolean exclusiveConsumer; private boolean alwaysSyncSend; From fa14a9cd75c52ded1d5c8637c3f34177ddc6b490 Mon Sep 17 00:00:00 2001 From: pradeep85841 Date: Wed, 11 Mar 2026 21:49:21 +0530 Subject: [PATCH 4/7] Align default configuration and tests with Jakarta 3.1 compliance --- .../activemq/ActiveMQConnectionFactory.java | 2 +- .../activemq/ActiveMQMessagePropertyTest.java | 119 ++++++++++++------ 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java index ae57d2624b6..c17a209d965 100644 --- a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java +++ b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java @@ -135,7 +135,7 @@ public class ActiveMQConnectionFactory extends JNDIBaseStorable implements Conne private int closeTimeout = 15000; private boolean useRetroactiveConsumer; private boolean exclusiveConsumer; - private boolean nestedMapAndListEnabled = true; + private boolean nestedMapAndListEnabled = false; private boolean alwaysSyncSend; private boolean watchTopicAdvisories = true; private int producerWindowSize = DEFAULT_PRODUCER_WINDOW_SIZE; diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java index 8ea8d15c4b2..1df00c14ffd 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java @@ -16,69 +16,116 @@ */ package org.apache.activemq; +import org.apache.activemq.broker.BrokerService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + import jakarta.jms.Connection; import jakarta.jms.Message; import jakarta.jms.MessageFormatException; import jakarta.jms.Session; -import org.junit.Test; + import static org.junit.Assert.fail; public class ActiveMQMessagePropertyTest { + private BrokerService broker; + private String connectionUri; + + @Before + public void setUp() throws Exception { + // Explicitly start the broker so we control its lifecycle + broker = new BrokerService(); + broker.setPersistent(false); + broker.setUseJmx(false); + broker.addConnector("vm://localhost"); + broker.start(); + broker.waitUntilStarted(); + + connectionUri = broker.getTransportConnectors().get(0).getPublishableConnectString(); + } + + @After + public void tearDown() throws Exception { + // Explicitly stop the broker to prevent flaky CI tests + if (broker != null) { + broker.stop(); + broker.waitUntilStopped(); + } + } + + @Test + public void testStrictComplianceRejectsCharacterByDefault() throws Exception { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); + + // Using try-with-resources for auto-closing connections and sessions + try (Connection connection = factory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + + Message message = session.createMessage(); + + try { + message.setObjectProperty("testChar", 'A'); + fail("Should have thrown MessageFormatException for Character type"); + } catch (MessageFormatException e) { + // strict Jakarta 3.1 compliance rejects Character + } + } + } + @Test public void testSetObjectPropertyCompliance() throws Exception { - ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false"); + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); // Test 1: Strict Mode (TCK Scenario) factory.setNestedMapAndListEnabled(false); - Connection strictConn = factory.createConnection(); - Session strictSession = strictConn.createSession(false, Session.AUTO_ACKNOWLEDGE); - Message strictMsg = strictSession.createMessage(); - - try { - strictMsg.setObjectProperty("charProp", 'A'); - fail("Strict mode (TCK) should reject Character type"); - } catch (MessageFormatException e) { - // Correct for Jakarta 3.1 + try (Connection strictConn = factory.createConnection(); + Session strictSession = strictConn.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + + Message strictMsg = strictSession.createMessage(); + try { + strictMsg.setObjectProperty("charProp", 'A'); + fail("Strict mode (TCK) should reject Character type"); + } catch (MessageFormatException e) { + // Correct for Jakarta 3.1 + } } - strictConn.close(); // Test 2: Legacy Mode (Backward Compatibility Scenario) factory.setNestedMapAndListEnabled(true); - Connection legacyConn = factory.createConnection(); - Session legacySession = legacyConn.createSession(false, Session.AUTO_ACKNOWLEDGE); - Message legacyMsg = legacySession.createMessage(); - - try { - legacyMsg.setObjectProperty("charProp", 'A'); - // legacy support is preserved! - } catch (MessageFormatException e) { - fail("Legacy mode should still allow Character to avoid breaking existing users"); + try (Connection legacyConn = factory.createConnection(); + Session legacySession = legacyConn.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + + Message legacyMsg = legacySession.createMessage(); + try { + legacyMsg.setObjectProperty("charProp", 'A'); + // legacy support is preserved! + } catch (MessageFormatException e) { + fail("Legacy mode should still allow Character to avoid breaking existing users"); + } } - legacyConn.close(); } @Test public void testLegacyValidationAllowsNonSpecTypes() throws Exception { - ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false"); + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); // Default ActiveMQ legacy behavior factory.setNestedMapAndListEnabled(true); - Connection connection = factory.createConnection(); - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - Message message = session.createMessage(); + try (Connection connection = factory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { - // Validates that Character/Map types are still accepted when legacy support is enabled. - try { - message.setObjectProperty("charProperty", 'A'); - message.setObjectProperty("mapProperty", new java.util.HashMap<>()); - } catch (MessageFormatException e) { - fail("Legacy mode should allow Character and Map types, but threw: " + e.getMessage()); - } + Message message = session.createMessage(); - connection.close(); + // Validates that Character/Map types are still accepted when legacy support is enabled. + try { + message.setObjectProperty("charProperty", 'A'); + message.setObjectProperty("mapProperty", new java.util.HashMap<>()); + } catch (MessageFormatException e) { + fail("Legacy mode should allow Character and Map types, but threw: " + e.getMessage()); + } + } } - - } From a03670c822c25fe372ff949a7a01f4227d4f14a2 Mon Sep 17 00:00:00 2001 From: pradeep85841 Date: Wed, 11 Mar 2026 23:37:53 +0530 Subject: [PATCH 5/7] Introduce strictCompliance master flag for Jakarta 3.1 alignment --- .../apache/activemq/ActiveMQConnection.java | 22 +++++- .../activemq/ActiveMQConnectionFactory.java | 25 ++++++- .../activemq/command/ActiveMQMessage.java | 7 ++ .../activemq/ActiveMQMessagePropertyTest.java | 71 +++++-------------- 4 files changed, 68 insertions(+), 57 deletions(-) diff --git a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java index ac8092386a2..c4cd05beaab 100644 --- a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java +++ b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java @@ -141,6 +141,13 @@ public class ActiveMQConnection implements Connection, TopicConnection, QueueCon private RedeliveryPolicyMap redeliveryPolicyMap; private MessageTransformer transformer; + /** + * If set to true, strict Jakarta Messaging 3.1 compliance is enforced. + * This rejects non-standard property types (like Character) and forces + * legacy features like nestedMapAndListEnabled to false. + */ + private boolean strictCompliance = false; + private boolean disableTimeStampsByDefault; private boolean optimizedMessageDispatch = true; private boolean copyMessageOnSend = true; @@ -150,7 +157,7 @@ public class ActiveMQConnection implements Connection, TopicConnection, QueueCon private boolean optimizeAcknowledge; private long optimizeAcknowledgeTimeOut = 0; private long optimizedAckScheduledAckInterval = 0; - private boolean nestedMapAndListEnabled = false; + private boolean nestedMapAndListEnabled = true; private boolean useRetroactiveConsumer; private boolean exclusiveConsumer; private boolean alwaysSyncSend; @@ -889,7 +896,7 @@ public ConnectionConsumer createSharedDurableConnectionConsumer(Topic topic, Str int maxMessages) throws JMSException { throw new UnsupportedOperationException("createSharedConnectionConsumer() is not supported"); } - + // Properties // ------------------------------------------------------------------------- @@ -1035,6 +1042,17 @@ public void setNestedMapAndListEnabled(boolean structuredMapsEnabled) { this.nestedMapAndListEnabled = structuredMapsEnabled; } + public boolean isStrictCompliance() { + return strictCompliance; + } + + /** + * Sets whether strict Jakarta Messaging compliance is enforced for this connection. + */ + public void setStrictCompliance(boolean strictCompliance) { + this.strictCompliance = strictCompliance; + } + public boolean isExclusiveConsumer() { return exclusiveConsumer; } diff --git a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java index c17a209d965..bf3128a0315 100644 --- a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java +++ b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java @@ -123,6 +123,13 @@ public class ActiveMQConnectionFactory extends JNDIBaseStorable implements Conne private BlobTransferPolicy blobTransferPolicy = new BlobTransferPolicy(); private MessageTransformer transformer; + /** + * If set to true, strict Jakarta Messaging 3.1 compliance is enforced. + * This rejects non-standard property types (like Character) and forces + * legacy features like nestedMapAndListEnabled to false. + */ + private boolean strictCompliance = false; + private boolean disableTimeStampsByDefault; private boolean optimizedMessageDispatch = true; private long optimizeAcknowledgeTimeOut = 300; @@ -135,7 +142,7 @@ public class ActiveMQConnectionFactory extends JNDIBaseStorable implements Conne private int closeTimeout = 15000; private boolean useRetroactiveConsumer; private boolean exclusiveConsumer; - private boolean nestedMapAndListEnabled = false; + private boolean nestedMapAndListEnabled = true; private boolean alwaysSyncSend; private boolean watchTopicAdvisories = true; private int producerWindowSize = DEFAULT_PRODUCER_WINDOW_SIZE; @@ -1000,6 +1007,22 @@ public void setNestedMapAndListEnabled(boolean structuredMapsEnabled) { this.nestedMapAndListEnabled = structuredMapsEnabled; } + public boolean isStrictCompliance() { + return strictCompliance; + } + + /** + * If this flag is set then the connection follows strict Jakarta Messaging + * compliance. Enabling this will automatically force nestedMapAndListEnabled to false. + */ + public void setStrictCompliance(boolean strictCompliance) { + this.strictCompliance = strictCompliance; + if (strictCompliance) { + this.setNestedMapAndListEnabled(false); + LOG.info("Strict compliance enabled: nestedMapAndListEnabled has been forced to false to align with Jakarta specifications."); + } + } + public String getClientIDPrefix() { return clientIDPrefix; } diff --git a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java index d393600d1f6..3ec94e4e39f 100644 --- a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java +++ b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java @@ -533,6 +533,13 @@ protected void checkValidObject(Object value) throws MessageFormatException { if (!valid) { ActiveMQConnection conn = getConnection(); + + // Jakarta 3.1 Compliance: If strictCompliance is enabled, only allow standard types. + // This acts as the "Master Switch" and overrides legacy behavior. + if (conn != null && conn.isStrictCompliance()) { + throw new MessageFormatException("Only objectified primitive objects and String types are allowed when strictCompliance is enabled but was: " + value + " type: " + value.getClass()); + } + // conn is null if we are in the broker rather than a JMS client if (conn == null || conn.isNestedMapAndListEnabled()) { // Standard rules: only primitives and Strings are allowed diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java index 1df00c14ffd..cb0ea1e3bfd 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java @@ -26,6 +26,8 @@ import jakarta.jms.MessageFormatException; import jakarta.jms.Session; +import static org.apache.activemq.command.DataStructureTestSupport.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; public class ActiveMQMessagePropertyTest { @@ -35,20 +37,17 @@ public class ActiveMQMessagePropertyTest { @Before public void setUp() throws Exception { - // Explicitly start the broker so we control its lifecycle broker = new BrokerService(); broker.setPersistent(false); broker.setUseJmx(false); broker.addConnector("vm://localhost"); broker.start(); broker.waitUntilStarted(); - connectionUri = broker.getTransportConnectors().get(0).getPublishableConnectString(); } @After public void tearDown() throws Exception { - // Explicitly stop the broker to prevent flaky CI tests if (broker != null) { broker.stop(); broker.waitUntilStopped(); @@ -56,76 +55,40 @@ public void tearDown() throws Exception { } @Test - public void testStrictComplianceRejectsCharacterByDefault() throws Exception { + public void testStrictComplianceMasterSwitch() throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); - // Using try-with-resources for auto-closing connections and sessions + // Enable master switch + factory.setStrictCompliance(true); + + // Verify side-effect + assertFalse("nestedMapAndListEnabled must be false when strictCompliance is true", + factory.isNestedMapAndListEnabled()); + try (Connection connection = factory.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { Message message = session.createMessage(); - try { - message.setObjectProperty("testChar", 'A'); - fail("Should have thrown MessageFormatException for Character type"); + message.setObjectProperty("charProp", 'A'); + fail("Should have rejected Character under strictCompliance=true"); } catch (MessageFormatException e) { - // strict Jakarta 3.1 compliance rejects Character + // Success } } } @Test - public void testSetObjectPropertyCompliance() throws Exception { + public void testLegacyModeStillAllowsCharacter() throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); - // Test 1: Strict Mode (TCK Scenario) - factory.setNestedMapAndListEnabled(false); - try (Connection strictConn = factory.createConnection(); - Session strictSession = strictConn.createSession(false, Session.AUTO_ACKNOWLEDGE)) { - - Message strictMsg = strictSession.createMessage(); - try { - strictMsg.setObjectProperty("charProp", 'A'); - fail("Strict mode (TCK) should reject Character type"); - } catch (MessageFormatException e) { - // Correct for Jakarta 3.1 - } - } - - // Test 2: Legacy Mode (Backward Compatibility Scenario) - factory.setNestedMapAndListEnabled(true); - try (Connection legacyConn = factory.createConnection(); - Session legacySession = legacyConn.createSession(false, Session.AUTO_ACKNOWLEDGE)) { - - Message legacyMsg = legacySession.createMessage(); - try { - legacyMsg.setObjectProperty("charProp", 'A'); - // legacy support is preserved! - } catch (MessageFormatException e) { - fail("Legacy mode should still allow Character to avoid breaking existing users"); - } - } - } - - @Test - public void testLegacyValidationAllowsNonSpecTypes() throws Exception { - ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); - - // Default ActiveMQ legacy behavior - factory.setNestedMapAndListEnabled(true); - + // Default is strictCompliance = false try (Connection connection = factory.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { Message message = session.createMessage(); - - // Validates that Character/Map types are still accepted when legacy support is enabled. - try { - message.setObjectProperty("charProperty", 'A'); - message.setObjectProperty("mapProperty", new java.util.HashMap<>()); - } catch (MessageFormatException e) { - fail("Legacy mode should allow Character and Map types, but threw: " + e.getMessage()); - } + message.setObjectProperty("charProp", 'A'); // Should pass + assertEquals('A', message.getObjectProperty("charProp")); } } } From 37704700c674f58d39f85b90a8cbdba92c20d370 Mon Sep 17 00:00:00 2001 From: pradeep85841 Date: Thu, 12 Mar 2026 00:57:55 +0530 Subject: [PATCH 6/7] Add strictCompliance flag and preserve legacy validation logic --- .../activemq/ActiveMQConnectionFactory.java | 5 ++++ .../activemq/command/ActiveMQMessage.java | 25 ++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java index bf3128a0315..00eaad14a09 100644 --- a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java +++ b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java @@ -415,6 +415,11 @@ protected ActiveMQConnection createActiveMQConnection(String userName, String pa protected ActiveMQConnection createActiveMQConnection(Transport transport, JMSStatsImpl stats) throws Exception { ActiveMQConnection connection = new ActiveMQConnection(transport, getClientIdGenerator(), getConnectionIdGenerator(), stats); + + // Copy the compliance flags from the Factory to the Connection + connection.setStrictCompliance(isStrictCompliance()); + connection.setNestedMapAndListEnabled(isNestedMapAndListEnabled()); + return connection; } diff --git a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java index 3ec94e4e39f..ca32539db29 100644 --- a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java +++ b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java @@ -525,26 +525,21 @@ public void setProperties(Map properties) throws JMSException { } protected void checkValidObject(Object value) throws MessageFormatException { - // Types allowed by the Jakarta Messaging Specification - boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || - value instanceof Integer || value instanceof Long || value instanceof Float || - value instanceof Double || value instanceof String || value == null; - if (!valid) { - - ActiveMQConnection conn = getConnection(); + boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long; + valid = valid || value instanceof Float || value instanceof Double || value instanceof String || value == null; - // Jakarta 3.1 Compliance: If strictCompliance is enabled, only allow standard types. - // This acts as the "Master Switch" and overrides legacy behavior. - if (conn != null && conn.isStrictCompliance()) { - throw new MessageFormatException("Only objectified primitive objects and String types are allowed when strictCompliance is enabled but was: " + value + " type: " + value.getClass()); - } + ActiveMQConnection conn = getConnection(); + // Legacy behavior: Character is valid ONLY if strictCompliance is OFF + if (conn == null || !conn.isStrictCompliance()) { + valid = valid || value instanceof Character; + } + if (!valid) { // conn is null if we are in the broker rather than a JMS client if (conn == null || conn.isNestedMapAndListEnabled()) { - // Standard rules: only primitives and Strings are allowed - if (!(value instanceof Map || value instanceof List || value instanceof Character)) { - throw new MessageFormatException("Only objectified primitive objects, String, Map, List and Character types are allowed but was: " + value + " type: " + value.getClass()); + if (!(value instanceof Map || value instanceof List)) { + throw new MessageFormatException("Only objectified primitive objects, String, Map and List types are allowed but was: " + value + " type: " + value.getClass()); } } else { throw new MessageFormatException("Only objectified primitive objects and String types are allowed but was: " + value + " type: " + value.getClass()); From 6fdef5a7098902914bdbd64b22415fcf5394be3c Mon Sep 17 00:00:00 2001 From: pradeep85841 Date: Thu, 12 Mar 2026 22:00:50 +0530 Subject: [PATCH 7/7] Decouple strictCompliance from legacy flags and isolate Character rejection --- .../apache/activemq/ActiveMQConnection.java | 3 +- .../activemq/ActiveMQConnectionFactory.java | 11 +---- .../activemq/command/ActiveMQMessage.java | 13 ++++-- .../activemq/ActiveMQMessagePropertyTest.java | 45 +++++++++++++------ 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java index c4cd05beaab..d84ce028ba0 100644 --- a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java +++ b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnection.java @@ -143,8 +143,7 @@ public class ActiveMQConnection implements Connection, TopicConnection, QueueCon /** * If set to true, strict Jakarta Messaging 3.1 compliance is enforced. - * This rejects non-standard property types (like Character) and forces - * legacy features like nestedMapAndListEnabled to false. + * This strictly rejects non-standard property types such as Character, Map, and List. */ private boolean strictCompliance = false; diff --git a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java index 00eaad14a09..b36f4a0d9da 100644 --- a/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java +++ b/activemq-client/src/main/java/org/apache/activemq/ActiveMQConnectionFactory.java @@ -125,8 +125,7 @@ public class ActiveMQConnectionFactory extends JNDIBaseStorable implements Conne /** * If set to true, strict Jakarta Messaging 3.1 compliance is enforced. - * This rejects non-standard property types (like Character) and forces - * legacy features like nestedMapAndListEnabled to false. + * This strictly rejects non-standard property types such as Character, Map, and List. */ private boolean strictCompliance = false; @@ -418,7 +417,6 @@ protected ActiveMQConnection createActiveMQConnection(Transport transport, JMSSt // Copy the compliance flags from the Factory to the Connection connection.setStrictCompliance(isStrictCompliance()); - connection.setNestedMapAndListEnabled(isNestedMapAndListEnabled()); return connection; } @@ -1017,15 +1015,10 @@ public boolean isStrictCompliance() { } /** - * If this flag is set then the connection follows strict Jakarta Messaging - * compliance. Enabling this will automatically force nestedMapAndListEnabled to false. + * If this flag is set then the connection follows strict Jakarta Messaging compliance. */ public void setStrictCompliance(boolean strictCompliance) { this.strictCompliance = strictCompliance; - if (strictCompliance) { - this.setNestedMapAndListEnabled(false); - LOG.info("Strict compliance enabled: nestedMapAndListEnabled has been forced to false to align with Jakarta specifications."); - } } public String getClientIDPrefix() { diff --git a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java index ca32539db29..75cdd56e592 100644 --- a/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java +++ b/activemq-client/src/main/java/org/apache/activemq/command/ActiveMQMessage.java @@ -527,15 +527,20 @@ public void setProperties(Map properties) throws JMSException { protected void checkValidObject(Object value) throws MessageFormatException { boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long; - valid = valid || value instanceof Float || value instanceof Double || value instanceof String || value == null; + valid = valid || value instanceof Float || value instanceof Double || value instanceof Character || value instanceof String || value == null; ActiveMQConnection conn = getConnection(); - // Legacy behavior: Character is valid ONLY if strictCompliance is OFF - if (conn == null || !conn.isStrictCompliance()) { - valid = valid || value instanceof Character; + + // strict rejection for Character + if (valid && conn != null && conn.isStrictCompliance() && value instanceof Character) { + throw new MessageFormatException("Character type not allowed under strict Jakarta 3.1 compliance"); } if (!valid) { + // Check strict compliance at validation time without side-effecting the 'nested' flag + if (conn != null && conn.isStrictCompliance()) { + throw new MessageFormatException("Only objectified primitive objects and String types are allowed under strict Jakarta 3.1 compliance but was: " + value + " type: " + value.getClass()); + } // conn is null if we are in the broker rather than a JMS client if (conn == null || conn.isNestedMapAndListEnabled()) { if (!(value instanceof Map || value instanceof List)) { diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java index cb0ea1e3bfd..1180c825a9a 100644 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/ActiveMQMessagePropertyTest.java @@ -26,6 +26,9 @@ import jakarta.jms.MessageFormatException; import jakarta.jms.Session; +import java.util.HashMap; +import java.util.Map; + import static org.apache.activemq.command.DataStructureTestSupport.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; @@ -56,26 +59,40 @@ public void tearDown() throws Exception { @Test public void testStrictComplianceMasterSwitch() throws Exception { - ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(connectionUri); + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost"); - // Enable master switch + // Turn on strict compliance factory.setStrictCompliance(true); + // Explicitly turn on the legacy flag to prove strict mode overrides it + factory.setNestedMapAndListEnabled(true); - // Verify side-effect - assertFalse("nestedMapAndListEnabled must be false when strictCompliance is true", - factory.isNestedMapAndListEnabled()); + Connection connection = factory.createConnection(); + connection.start(); - try (Connection connection = factory.createConnection(); - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Message message = session.createMessage(); - Message message = session.createMessage(); - try { - message.setObjectProperty("charProp", 'A'); - fail("Should have rejected Character under strictCompliance=true"); - } catch (MessageFormatException e) { - // Success - } + // Verify standard types work + message.setStringProperty("validString", "test"); // Should pass + + // Verify Character is rejected + try { + message.setObjectProperty("invalidChar", 'A'); + fail("Should have rejected Character under strict compliance"); + } catch (MessageFormatException e) { + // Expected + } + + // Verify Map is rejected (even though nestedMapAndListEnabled is true) + try { + Map map = new HashMap<>(); + message.setObjectProperty("invalidMap", map); + fail("Should have rejected Map under strict compliance"); + } catch (MessageFormatException e) { + // Expected } + + connection.close(); } @Test