diff --git a/common/util/pom.xml b/common/util/pom.xml
index 8a0fa1705b..26c4b42920 100644
--- a/common/util/pom.xml
+++ b/common/util/pom.xml
@@ -74,6 +74,11 @@
junit
test
+
+ org.hamcrest
+ hamcrest
+ test
+
org.mockito
mockito-core
diff --git a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java
new file mode 100644
index 0000000000..7f91da737b
--- /dev/null
+++ b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java
@@ -0,0 +1,83 @@
+/**
+ * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
+ *
+ * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of ThingsBoard, Inc. and its suppliers,
+ * if any. The intellectual and technical concepts contained
+ * herein are proprietary to ThingsBoard, Inc.
+ * and its suppliers and may be covered by U.S. and Foreign Patents,
+ * patents in process, and are protected by trade secret or copyright law.
+ *
+ * Dissemination of this information or reproduction of this material is strictly forbidden
+ * unless prior written permission is obtained from COMPANY.
+ *
+ * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
+ * managers or contractors who have executed Confidentiality and Non-disclosure agreements
+ * explicitly covering such access.
+ *
+ * The copyright notice above does not evidence any actual or intended publication
+ * or disclosure of this source code, which includes
+ * information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
+ * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
+ * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
+ * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
+ * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
+ * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
+ * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
+ * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
+ */
+package org.thingsboard.common.util;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * HashMap that removed eldest (by put order) entries
+ * It guaranteed that size is not greater then maxEntries parameter. And remove time is constant O(1).
+ * Use withMaxEntries to setup maxEntries.
+ * Because overloaded constructor will look similar to LinkedHashMap(initCapacity)
+ * Example:
+ * new LinkedHashMapRemoveEldest().withMaxEntries(100L)
+ * */
+@Getter
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class LinkedHashMapRemoveEldest extends LinkedHashMap {
+ long maxEntries = Long.MAX_VALUE;
+
+ public LinkedHashMapRemoveEldest() {
+ super();
+ }
+
+ public LinkedHashMapRemoveEldest(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public LinkedHashMapRemoveEldest(int initialCapacity, float loadFactor) {
+ super(initialCapacity, loadFactor);
+ }
+
+ public LinkedHashMapRemoveEldest(Map extends K, ? extends V> m) {
+ super(m);
+ }
+
+ public LinkedHashMapRemoveEldest(int initialCapacity, float loadFactor, boolean accessOrder) {
+ super(initialCapacity, loadFactor, accessOrder);
+ }
+
+ public LinkedHashMapRemoveEldest withMaxEntries(long maxEntries) {
+ this.maxEntries = maxEntries;
+ return this;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > maxEntries;
+ }
+}
diff --git a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java
new file mode 100644
index 0000000000..4dad070ec4
--- /dev/null
+++ b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java
@@ -0,0 +1,70 @@
+/**
+ * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL
+ *
+ * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved.
+ *
+ * NOTICE: All information contained herein is, and remains
+ * the property of ThingsBoard, Inc. and its suppliers,
+ * if any. The intellectual and technical concepts contained
+ * herein are proprietary to ThingsBoard, Inc.
+ * and its suppliers and may be covered by U.S. and Foreign Patents,
+ * patents in process, and are protected by trade secret or copyright law.
+ *
+ * Dissemination of this information or reproduction of this material is strictly forbidden
+ * unless prior written permission is obtained from COMPANY.
+ *
+ * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees,
+ * managers or contractors who have executed Confidentiality and Non-disclosure agreements
+ * explicitly covering such access.
+ *
+ * The copyright notice above does not evidence any actual or intended publication
+ * or disclosure of this source code, which includes
+ * information that is confidential and/or proprietary, and is a trade secret, of COMPANY.
+ * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
+ * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT
+ * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED,
+ * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES.
+ * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION
+ * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS,
+ * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
+ */
+package org.thingsboard.common.util;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedHashMap;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class LinkedHashMapRemoveEldestTest {
+
+ public static final long MAX_ENTRIES = 10L;
+ LinkedHashMapRemoveEldest map;
+
+ @Before
+ public void setUp() throws Exception {
+ map = new LinkedHashMapRemoveEldest().withMaxEntries(MAX_ENTRIES);
+ }
+
+ @Test
+ public void givenMap_whenOverSized_thenVerifyRemovedEldest() {
+ assertThat(map.getMaxEntries(), is(MAX_ENTRIES));
+ assertThat(map, instanceOf(LinkedHashMap.class));
+ assertThat(map, instanceOf(LinkedHashMapRemoveEldest.class));
+ assertThat(map.size(), is(0));
+
+ for (long i = 0; i < MAX_ENTRIES * 2; i++) {
+ map.put(i, String.valueOf(i));
+ }
+
+ assertThat(map.size(), is((int) MAX_ENTRIES));
+
+ for (long i = MAX_ENTRIES; i < MAX_ENTRIES * 2; i++) {
+ assertThat(map.get(i), is(String.valueOf(i)));
+ }
+ }
+
+}
\ No newline at end of file