• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sonus21 / rqueue / 26012877262

18 May 2026 04:04AM UTC coverage: 83.319% (-0.09%) from 83.412%
26012877262

push

github

web-flow
fix: restore Jackson 2.x property order in RqueueRedisSerializer to prevent stale processing-queue entries after 3.x → 4.x upgrade (#300)

* fix: restore Jackson 2.x property order in RqueueRedisSerializer to prevent stale processing-queue entries after 3.x → 4.x upgrade

* build: bump version to 4.0.0-RC10

* feat: add rqueue.serialization.property.order property to control JSON field ordering

Introduces RqueueRedisSerializer.PropertyOrder enum (ALPHABETICAL | DECLARATION)
and wires it via rqueue.serialization.property.order (default: ALPHABETICAL).

ALPHABETICAL uses Jackson 3.x alphabetical ordering, the native default for
RQueue 4.x deployments. No configuration change required for new installs.

DECLARATION uses declaration order, matching the Jackson 2.x behaviour of
RQueue 3.x. Set this when upgrading from 3.x with messages still in Redis
queues, as switching while messages are in-flight causes unexpected retries.

The setting is applied in RqueueListenerBaseConfig before any Redis template is
created (overriding RedisUtils providers when DECLARATION is requested), and
flows through RqueueConfig to RqueueInternalPubSubChannel so all serialiser
instances in the application use the same order.

Docs: configuration.md and migrations.md updated with property description,
accepted values, and the 3.x → 4.x migration warning.

Assisted-By: Claude Sonnet 4.6

2627 of 3485 branches covered (75.38%)

Branch coverage included in aggregate %.

13 of 31 new or added lines in 3 files covered. (41.94%)

14 existing lines in 6 files now uncovered.

7847 of 9086 relevant lines covered (86.36%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.09
/rqueue-core/src/main/java/com/github/sonus21/rqueue/converter/RqueueRedisSerializer.java
1
/*
2
 * Copyright (c) 2020-2026 Sonu Kumar
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * You may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and limitations under the License.
14
 *
15
 */
16

17
package com.github.sonus21.rqueue.converter;
18

19
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
20
import com.github.sonus21.rqueue.serdes.SerializationUtils;
21
import lombok.extern.slf4j.Slf4j;
22
import org.springframework.cache.support.NullValue;
23
import org.springframework.data.redis.serializer.RedisSerializer;
24
import org.springframework.data.redis.serializer.SerializationException;
25
import tools.jackson.core.JacksonException;
26
import tools.jackson.core.JsonGenerator;
27
import tools.jackson.databind.DefaultTyping;
28
import tools.jackson.databind.MapperFeature;
29
import tools.jackson.databind.ObjectMapper;
30
import tools.jackson.databind.SerializationContext;
31
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
32
import tools.jackson.databind.module.SimpleModule;
33
import tools.jackson.databind.ser.std.StdSerializer;
34

35
@Slf4j
1✔
36
public class RqueueRedisSerializer implements RedisSerializer<Object> {
37

38
  /**
39
   * Controls JSON property ordering for {@link com.github.sonus21.rqueue.core.RqueueMessage}
40
   * serialisation. Configure via {@code rqueue.serialization.property.order}.
41
   *
42
   * <ul>
43
   *   <li>{@link #ALPHABETICAL} — alphabetical order, Jackson 3.x native behaviour. This is the
44
   *       default for RQueue 4.x.
45
   *   <li>{@link #DECLARATION} — declaration order, matching Jackson 2.x / RQueue 3.x. Use when
46
   *       upgrading from 3.x with messages still present in Redis queues.
47
   * </ul>
48
   */
49
  public enum PropertyOrder {
1✔
50
    ALPHABETICAL,
1✔
51
    DECLARATION
1✔
52
  }
53

54
  private final RedisSerializer<Object> serializer;
55

56
  public RqueueRedisSerializer(RedisSerializer<Object> redisSerializer) {
1✔
57
    this.serializer = redisSerializer;
1✔
58
  }
1✔
59

60
  /** Creates a serialiser using {@link PropertyOrder#ALPHABETICAL} (Jackson 3.x default). */
61
  public RqueueRedisSerializer() {
62
    this(PropertyOrder.ALPHABETICAL);
1✔
63
  }
1✔
64

65
  public RqueueRedisSerializer(PropertyOrder order) {
66
    this(new RqueueRedisSerDes(order));
1✔
67
  }
1✔
68

69
  @Override
70
  public byte[] serialize(Object t) throws SerializationException {
71
    return serializer.serialize(t);
1✔
72
  }
73

74
  @Override
75
  public Object deserialize(byte[] bytes) throws SerializationException {
76
    if (SerializationUtils.isEmpty(bytes)) {
1✔
77
      return null;
1✔
78
    }
79
    try {
80
      return serializer.deserialize(bytes);
1✔
81
    } catch (Exception e) {
×
82
      log.warn("Deserialization has failed {}", new String(bytes), e);
×
83
      return new String(bytes);
×
84
    }
85
  }
86

87
  // adapted from spring-data-redis
88
  private static class RqueueRedisSerDes implements RedisSerializer<Object> {
89
    private final ObjectMapper mapper;
90

91
    RqueueRedisSerDes(PropertyOrder order) {
1✔
92
      var builder = SerializationUtils.getObjectMapper()
1✔
93
          .rebuild()
1✔
94
          .addModule(new SimpleModule().addSerializer(new NullValueSerializer()))
1✔
95
          .activateDefaultTyping(
1✔
96
              BasicPolymorphicTypeValidator.builder()
1✔
97
                  .allowIfSubType(Object.class)
1✔
98
                  .build(),
1✔
99
              DefaultTyping.NON_FINAL,
100
              As.PROPERTY);
101
      if (order == PropertyOrder.DECLARATION) {
1!
NEW
102
        builder = builder.disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
×
103
      }
104
      this.mapper = builder.build();
1✔
105
    }
1✔
106

107
    @Override
108
    public byte[] serialize(Object source) throws SerializationException {
109
      if (source == null) {
1!
110
        return SerializationUtils.EMPTY_ARRAY;
×
111
      }
112
      try {
113
        return mapper.writeValueAsBytes(source);
1✔
114
      } catch (JacksonException e) {
×
115
        throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
×
116
      }
117
    }
118

119
    @Override
120
    public Object deserialize(byte[] source) throws SerializationException {
121
      if (SerializationUtils.isEmpty(source)) {
1!
122
        return null;
×
123
      }
124
      try {
125
        return mapper.readValue(source, Object.class);
1✔
126
      } catch (Exception ex) {
×
127
        throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
×
128
      }
129
    }
130

131
    private static class NullValueSerializer extends StdSerializer<NullValue> {
132

133
      private static final long serialVersionUID = 211020517180777825L;
134
      private final String classIdentifier;
135

136
      NullValueSerializer() {
137
        super(NullValue.class);
1✔
138
        this.classIdentifier = "@class";
1✔
139
      }
1✔
140

141
      @Override
142
      public void serialize(
143
          NullValue value, JsonGenerator jsonGenerator, SerializationContext provider)
144
          throws JacksonException {
145
        jsonGenerator.writeStartObject();
×
146
        jsonGenerator.writeStringProperty(classIdentifier, NullValue.class.getName());
×
147
        jsonGenerator.writeEndObject();
×
148
      }
×
149
    }
150
  }
151
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc