diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 76163831..e797c2f0 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -9,7 +9,7 @@ jobs:
matrix:
distribution: ['zulu']
os: [ubuntu-latest, windows-latest, macos-latest]
- version: [ 11, 17, 21, 22 ]
+ version: [ 17, 21, 24 ]
steps:
- uses: actions/checkout@v5
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f650c70..7b80d986 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+4.0.0
+------------------
+
+* Java 17 or greater is now required.
+
3.2.0 (2025-05-28)
------------------
diff --git a/README.md b/README.md
index c55f0f87..37f471c8 100644
--- a/README.md
+++ b/README.md
@@ -120,8 +120,8 @@ public class Lookup {
}
```
-You can also use the reader object to iterate over the database.
-The `reader.networks()` and `reader.networksWithin()` methods can
+You can also use the reader object to iterate over the database.
+The `reader.networks()` and `reader.networksWithin()` methods can
be used for this purpose.
```java
@@ -207,7 +207,7 @@ specific to this reader, please [contact MaxMind support](https://www.maxmind.co
## Requirements ##
-This API requires Java 11 or greater.
+This API requires Java 17 or greater.
## Contributing ##
diff --git a/pom.xml b/pom.xml
index b8d95115..70c31ea9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -147,9 +147,9 @@
maven-compiler-plugin
3.14.0
- 11
- 11
- 11
+ 17
+ 17
+ 17
diff --git a/sample/Benchmark.java b/sample/Benchmark.java
index 3d73011b..b0f8fd2e 100644
--- a/sample/Benchmark.java
+++ b/sample/Benchmark.java
@@ -45,7 +45,7 @@ private static void bench(Reader r, int count, int seed) throws IOException {
for (int i = 0; i < count; i++) {
random.nextBytes(address);
InetAddress ip = InetAddress.getByAddress(address);
- Map t = r.get(ip, Map.class);
+ Map t = r.get(ip, Map.class);
if (TRACE) {
if (i % 50000 == 0) {
System.out.println(i + " " + ip);
diff --git a/src/main/java/com/maxmind/db/CHMCache.java b/src/main/java/com/maxmind/db/CHMCache.java
index f9f63292..0b22d4cc 100644
--- a/src/main/java/com/maxmind/db/CHMCache.java
+++ b/src/main/java/com/maxmind/db/CHMCache.java
@@ -13,7 +13,7 @@ public class CHMCache implements NodeCache {
private static final int DEFAULT_CAPACITY = 4096;
private final int capacity;
- private final ConcurrentHashMap cache;
+ private final ConcurrentHashMap, DecodedValue> cache;
private boolean cacheFull = false;
/**
@@ -36,7 +36,7 @@ public CHMCache(int capacity) {
}
@Override
- public DecodedValue get(CacheKey key, Loader loader) throws IOException {
+ public DecodedValue get(CacheKey> key, Loader loader) throws IOException {
DecodedValue value = cache.get(key);
if (value == null) {
value = loader.load(key);
diff --git a/src/main/java/com/maxmind/db/CacheKey.java b/src/main/java/com/maxmind/db/CacheKey.java
index d62c0843..3a2c0d46 100644
--- a/src/main/java/com/maxmind/db/CacheKey.java
+++ b/src/main/java/com/maxmind/db/CacheKey.java
@@ -6,61 +6,9 @@
* of the value.
*
* @param the type of value
+ * @param offset the offset of the value in the database file
+ * @param cls the class of the value
+ * @param type the type of the value
*/
-public final class CacheKey {
- private final int offset;
- private final Class cls;
- private final java.lang.reflect.Type type;
-
- CacheKey(int offset, Class cls, java.lang.reflect.Type type) {
- this.offset = offset;
- this.cls = cls;
- this.type = type;
- }
-
- int getOffset() {
- return this.offset;
- }
-
- Class getCls() {
- return this.cls;
- }
-
- java.lang.reflect.Type getType() {
- return this.type;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == null) {
- return false;
- }
-
- CacheKey other = (CacheKey) o;
-
- if (this.offset != other.offset) {
- return false;
- }
-
- if (this.cls == null) {
- if (other.cls != null) {
- return false;
- }
- } else if (!this.cls.equals(other.cls)) {
- return false;
- }
-
- if (this.type == null) {
- return other.type == null;
- }
- return this.type.equals(other.type);
- }
-
- @Override
- public int hashCode() {
- int result = offset;
- result = 31 * result + (cls == null ? 0 : cls.hashCode());
- result = 31 * result + (type == null ? 0 : type.hashCode());
- return result;
- }
+public record CacheKey(int offset, Class cls, java.lang.reflect.Type type) {
}
diff --git a/src/main/java/com/maxmind/db/CachedConstructor.java b/src/main/java/com/maxmind/db/CachedConstructor.java
index 2b5b3160..c1c5f456 100644
--- a/src/main/java/com/maxmind/db/CachedConstructor.java
+++ b/src/main/java/com/maxmind/db/CachedConstructor.java
@@ -3,37 +3,10 @@
import java.lang.reflect.Constructor;
import java.util.Map;
-final class CachedConstructor {
- private final Constructor constructor;
- private final Class>[] parameterTypes;
- private final java.lang.reflect.Type[] parameterGenericTypes;
- private final Map parameterIndexes;
-
- CachedConstructor(
- Constructor constructor,
- Class>[] parameterTypes,
- java.lang.reflect.Type[] parameterGenericTypes,
- Map parameterIndexes
- ) {
- this.constructor = constructor;
- this.parameterTypes = parameterTypes;
- this.parameterGenericTypes = parameterGenericTypes;
- this.parameterIndexes = parameterIndexes;
- }
-
- Constructor getConstructor() {
- return this.constructor;
- }
-
- Class>[] getParameterTypes() {
- return this.parameterTypes;
- }
-
- java.lang.reflect.Type[] getParameterGenericTypes() {
- return this.parameterGenericTypes;
- }
-
- Map getParameterIndexes() {
- return this.parameterIndexes;
- }
+record CachedConstructor(
+ Constructor constructor,
+ Class>[] parameterTypes,
+ java.lang.reflect.Type[] parameterGenericTypes,
+ Map parameterIndexes
+) {
}
diff --git a/src/main/java/com/maxmind/db/CtrlData.java b/src/main/java/com/maxmind/db/CtrlData.java
index c9bf03f1..ff9d0ce9 100644
--- a/src/main/java/com/maxmind/db/CtrlData.java
+++ b/src/main/java/com/maxmind/db/CtrlData.java
@@ -1,31 +1,4 @@
package com.maxmind.db;
-final class CtrlData {
- private final Type type;
- private final int ctrlByte;
- private final int offset;
- private final int size;
-
- CtrlData(Type type, int ctrlByte, int offset, int size) {
- this.type = type;
- this.ctrlByte = ctrlByte;
- this.offset = offset;
- this.size = size;
- }
-
- public Type getType() {
- return this.type;
- }
-
- public int getCtrlByte() {
- return this.ctrlByte;
- }
-
- public int getOffset() {
- return this.offset;
- }
-
- public int getSize() {
- return this.size;
- }
+record CtrlData(Type type, int ctrlByte, int offset, int size) {
}
diff --git a/src/main/java/com/maxmind/db/Decoder.java b/src/main/java/com/maxmind/db/Decoder.java
index ab9a0218..c1c7446d 100644
--- a/src/main/java/com/maxmind/db/Decoder.java
+++ b/src/main/java/com/maxmind/db/Decoder.java
@@ -36,7 +36,7 @@ class Decoder {
private final ByteBuffer buffer;
- private final ConcurrentHashMap constructors;
+ private final ConcurrentHashMap, CachedConstructor>> constructors;
Decoder(NodeCache cache, ByteBuffer buffer, long pointerBase) {
this(
@@ -51,7 +51,7 @@ class Decoder {
NodeCache cache,
ByteBuffer buffer,
long pointerBase,
- ConcurrentHashMap constructors
+ ConcurrentHashMap, CachedConstructor>> constructors
) {
this.cache = cache;
this.pointerBase = pointerBase;
@@ -73,7 +73,7 @@ T decode(int offset, Class cls) throws IOException {
}
private DecodedValue decode(CacheKey key) throws IOException {
- int offset = key.getOffset();
+ int offset = key.offset();
if (offset >= this.buffer.capacity()) {
throw new InvalidDatabaseException(
"The MaxMind DB file's data section contains bad data: "
@@ -81,8 +81,8 @@ private DecodedValue decode(CacheKey key) throws IOException {
}
this.buffer.position(offset);
- Class cls = key.getCls();
- return decode(cls, key.getType());
+ Class cls = key.cls();
+ return decode(cls, key.type());
}
private DecodedValue decode(Class cls, java.lang.reflect.Type genericType)
@@ -120,16 +120,11 @@ private DecodedValue decode(Class cls, java.lang.reflect.Type genericType
int size = ctrlByte & 0x1f;
if (size >= 29) {
- switch (size) {
- case 29:
- size = 29 + (0xFF & buffer.get());
- break;
- case 30:
- size = 285 + decodeInteger(2);
- break;
- default:
- size = 65821 + decodeInteger(3);
- }
+ size = switch (size) {
+ case 29 -> 29 + (0xFF & buffer.get());
+ case 30 -> 285 + decodeInteger(2);
+ default -> 65821 + decodeInteger(3);
+ };
}
return new DecodedValue(this.decodeByType(type, size, cls, genericType));
@@ -140,12 +135,12 @@ DecodedValue decodePointer(long pointer, Class> cls, java.lang.reflect.Type ge
int targetOffset = (int) pointer;
int position = buffer.position();
- CacheKey key = new CacheKey(targetOffset, cls, genericType);
+ CacheKey> key = new CacheKey<>(targetOffset, cls, genericType);
DecodedValue o = cache.get(key, cacheLoader);
buffer.position(position);
return o;
- }
+ }
private Object decodeByType(
Type type,
@@ -158,8 +153,7 @@ private Object decodeByType(
return this.decodeMap(size, cls, genericType);
case ARRAY:
Class> elementClass = Object.class;
- if (genericType instanceof ParameterizedType) {
- ParameterizedType ptype = (ParameterizedType) genericType;
+ if (genericType instanceof ParameterizedType ptype) {
java.lang.reflect.Type[] actualTypes = ptype.getActualTypeArguments();
if (actualTypes.length == 1) {
elementClass = (Class>) actualTypes[0];
@@ -260,16 +254,13 @@ private float decodeFloat(int size) throws InvalidDatabaseException {
private static boolean decodeBoolean(int size)
throws InvalidDatabaseException {
- switch (size) {
- case 0:
- return false;
- case 1:
- return true;
- default:
- throw new InvalidDatabaseException(
- "The MaxMind DB file's data section contains bad data: "
- + "invalid size of boolean.");
- }
+ return switch (size) {
+ case 0 -> false;
+ case 1 -> true;
+ default -> throw new InvalidDatabaseException(
+ "The MaxMind DB file's data section contains bad data: "
+ + "invalid size of boolean.");
+ };
}
private List decodeArray(
@@ -319,8 +310,7 @@ private Object decodeMap(
) throws IOException {
if (Map.class.isAssignableFrom(cls) || cls.equals(Object.class)) {
Class> valueClass = Object.class;
- if (genericType instanceof ParameterizedType) {
- ParameterizedType ptype = (ParameterizedType) genericType;
+ if (genericType instanceof ParameterizedType ptype) {
java.lang.reflect.Type[] actualTypes = ptype.getActualTypeArguments();
if (actualTypes.length == 2) {
Class> keyClass = (Class>) actualTypes[0];
@@ -381,7 +371,7 @@ private Map decodeMapIntoMap(
private Object decodeMapIntoObject(int size, Class cls)
throws IOException {
- CachedConstructor cachedConstructor = this.constructors.get(cls);
+ CachedConstructor cachedConstructor = getCachedConstructor(cls);
Constructor constructor;
Class>[] parameterTypes;
java.lang.reflect.Type[] parameterGenericTypes;
@@ -402,7 +392,7 @@ private Object decodeMapIntoObject(int size, Class cls)
this.constructors.put(
cls,
- new CachedConstructor(
+ new CachedConstructor<>(
constructor,
parameterTypes,
parameterGenericTypes,
@@ -410,10 +400,10 @@ private Object decodeMapIntoObject(int size, Class cls)
)
);
} else {
- constructor = cachedConstructor.getConstructor();
- parameterTypes = cachedConstructor.getParameterTypes();
- parameterGenericTypes = cachedConstructor.getParameterGenericTypes();
- parameterIndexes = cachedConstructor.getParameterIndexes();
+ constructor = cachedConstructor.constructor();
+ parameterTypes = cachedConstructor.parameterTypes();
+ parameterGenericTypes = cachedConstructor.parameterGenericTypes();
+ parameterIndexes = cachedConstructor.parameterIndexes();
}
Object[] parameters = new Object[parameterTypes.length];
@@ -455,6 +445,13 @@ private Object decodeMapIntoObject(int size, Class cls)
}
}
+ private CachedConstructor getCachedConstructor(Class cls) {
+ // This cast is safe because we only put CachedConstructor for Class as the key
+ @SuppressWarnings("unchecked")
+ CachedConstructor result = (CachedConstructor) this.constructors.get(cls);
+ return result;
+ }
+
private static Constructor findConstructor(Class cls)
throws ConstructorNotFoundException {
Constructor>[] constructors = cls.getConstructors();
@@ -495,11 +492,11 @@ private int nextValueOffset(int offset, int numberToSkip)
}
CtrlData ctrlData = this.getCtrlData(offset);
- int ctrlByte = ctrlData.getCtrlByte();
- int size = ctrlData.getSize();
- offset = ctrlData.getOffset();
+ int ctrlByte = ctrlData.ctrlByte();
+ int size = ctrlData.size();
+ offset = ctrlData.offset();
- Type type = ctrlData.getType();
+ Type type = ctrlData.type();
switch (type) {
case POINTER:
int pointerSize = ((ctrlByte >>> 3) & 0x3) + 1;
@@ -555,16 +552,11 @@ private CtrlData getCtrlData(int offset)
if (size >= 29) {
int bytesToRead = size - 28;
offset += bytesToRead;
- switch (size) {
- case 29:
- size = 29 + (0xFF & buffer.get());
- break;
- case 30:
- size = 285 + decodeInteger(2);
- break;
- default:
- size = 65821 + decodeInteger(3);
- }
+ size = switch (size) {
+ case 29 -> 29 + (0xFF & buffer.get());
+ case 30 -> 285 + decodeInteger(2);
+ default -> 65821 + decodeInteger(3);
+ };
}
return new CtrlData(type, ctrlByte, offset, size);
diff --git a/src/main/java/com/maxmind/db/Networks.java b/src/main/java/com/maxmind/db/Networks.java
index 93913c60..da6a0056 100644
--- a/src/main/java/com/maxmind/db/Networks.java
+++ b/src/main/java/com/maxmind/db/Networks.java
@@ -32,7 +32,7 @@ public final class Networks implements Iterator> {
*/
Networks(Reader reader, boolean includeAliasedNetworks, Class typeParameterClass)
throws ClosedDatabaseException {
- this(reader, includeAliasedNetworks, new NetworkNode[]{}, typeParameterClass);
+ this(reader, includeAliasedNetworks, new NetworkNode[0], typeParameterClass);
}
/**
diff --git a/src/main/java/com/maxmind/db/NetworksIterationException.java b/src/main/java/com/maxmind/db/NetworksIterationException.java
index ad24a88c..5f13c9d8 100644
--- a/src/main/java/com/maxmind/db/NetworksIterationException.java
+++ b/src/main/java/com/maxmind/db/NetworksIterationException.java
@@ -5,14 +5,14 @@
* This class represents an error encountered while iterating over the networks.
* The most likely causes are corrupt data in the database, or a bug in the reader code.
*
- *
+ *
* This exception extends RuntimeException because it is thrown by the iterator
* methods in {@link Networks}.
*
*
* @see Networks
*/
-public class NetworksIterationException extends RuntimeException {
+public class NetworksIterationException extends RuntimeException {
NetworksIterationException(String message) {
super(message);
}
diff --git a/src/main/java/com/maxmind/db/NoCache.java b/src/main/java/com/maxmind/db/NoCache.java
index f169e3b3..43cda527 100644
--- a/src/main/java/com/maxmind/db/NoCache.java
+++ b/src/main/java/com/maxmind/db/NoCache.java
@@ -13,10 +13,10 @@ private NoCache() {
}
@Override
- public DecodedValue get(CacheKey key, Loader loader) throws IOException {
+ public DecodedValue get(CacheKey> key, Loader loader) throws IOException {
return loader.load(key);
}
-
+
/**
* @return the singleton instance of the NoCache class
*/
diff --git a/src/main/java/com/maxmind/db/NodeCache.java b/src/main/java/com/maxmind/db/NodeCache.java
index d7235813..4dd9175c 100644
--- a/src/main/java/com/maxmind/db/NodeCache.java
+++ b/src/main/java/com/maxmind/db/NodeCache.java
@@ -18,12 +18,12 @@ interface Loader {
* @throws IOException
* if there is an error loading the value
*/
- DecodedValue load(CacheKey key) throws IOException;
+ DecodedValue load(CacheKey> key) throws IOException;
}
/**
* This method returns the value for the key. If the key is not in the cache
- * then the loader is called to load the value.
+ * then the loader is called to load the value.
*
* @param key
* the key to look up
@@ -33,6 +33,6 @@ interface Loader {
* @throws IOException
* if there is an error loading the value
*/
- DecodedValue get(CacheKey key, Loader loader) throws IOException;
+ DecodedValue get(CacheKey> key, Loader loader) throws IOException;
}
diff --git a/src/main/java/com/maxmind/db/Reader.java b/src/main/java/com/maxmind/db/Reader.java
index 15609c95..c9e41591 100644
--- a/src/main/java/com/maxmind/db/Reader.java
+++ b/src/main/java/com/maxmind/db/Reader.java
@@ -26,7 +26,7 @@ public final class Reader implements Closeable {
private final Metadata metadata;
private final AtomicReference bufferHolderReference;
private final NodeCache cache;
- private final ConcurrentHashMap constructors;
+ private final ConcurrentHashMap, CachedConstructor>> constructors;
/**
* The file mode to use when opening a MaxMind DB.
diff --git a/src/test/java/com/maxmind/db/DecoderTest.java b/src/test/java/com/maxmind/db/DecoderTest.java
index f6e23569..92d6f3b1 100644
--- a/src/test/java/com/maxmind/db/DecoderTest.java
+++ b/src/test/java/com/maxmind/db/DecoderTest.java
@@ -465,8 +465,8 @@ private static void testTypeDecoding(Type type, Map tests)
String key = (String) keyObject;
Object value = expectMap.get(key);
- if (value instanceof Object[]) {
- assertArrayEquals((Object[]) value, (Object[]) got.get(key), desc);
+ if (value instanceof Object[] arrayValue) {
+ assertArrayEquals(arrayValue, (Object[]) got.get(key), desc);
} else {
assertEquals(value, got.get(key), desc);
}
diff --git a/src/test/java/com/maxmind/db/ReaderTest.java b/src/test/java/com/maxmind/db/ReaderTest.java
index 1925a9fb..d2ff0904 100644
--- a/src/test/java/com/maxmind/db/ReaderTest.java
+++ b/src/test/java/com/maxmind/db/ReaderTest.java
@@ -172,7 +172,7 @@ public networkTest(String network, int prefix,String database, String[] expecte
}
),
new networkTest(
- "255.255.255.0",
+ "255.255.255.0",
24,
"ipv4",
new String[]{}
@@ -238,7 +238,7 @@ public networkTest(String network, int prefix,String database, String[] expecte
"1.1.1.4/30",
"1.1.1.8/29",
"1.1.1.16/28",
- "1.1.1.32/32",
+ "1.1.1.32/32",
}
),
new networkTest(
@@ -251,7 +251,7 @@ public networkTest(String network, int prefix,String database, String[] expecte
"1.1.1.4/30",
"1.1.1.8/29",
"1.1.1.16/28",
- "1.1.1.32/32",
+ "1.1.1.32/32",
},
true
),
@@ -555,12 +555,12 @@ private void testDecodingTypesIntoModelObject(Reader reader, boolean booleanValu
assertEquals("unicode! ☯ - ♫", model.utf8StringField);
- List expectedArray = new ArrayList<>(Arrays.asList(
+ List expectedArray = new ArrayList<>(List.of(
(long) 1, (long) 2, (long) 3
));
assertEquals(expectedArray, model.arrayField);
- List expectedArray2 = new ArrayList<>(Arrays.asList(
+ List expectedArray2 = new ArrayList<>(List.of(
(long) 7, (long) 8, (long) 9
));
assertEquals(expectedArray2, model.mapField.mapXField.arrayXField);
@@ -675,12 +675,12 @@ private void testDecodingTypesIntoModelObjectBoxed(Reader reader, boolean boolea
assertEquals("unicode! ☯ - ♫", model.utf8StringField);
- List expectedArray = new ArrayList<>(Arrays.asList(
+ List expectedArray = new ArrayList<>(List.of(
(long) 1, (long) 2, (long) 3
));
assertEquals(expectedArray, model.arrayField);
- List expectedArray2 = new ArrayList<>(Arrays.asList(
+ List expectedArray2 = new ArrayList<>(List.of(
(long) 7, (long) 8, (long) 9
));
assertEquals(expectedArray2, model.mapField.mapXField.arrayXField);
@@ -785,7 +785,7 @@ private void testDecodingTypesIntoModelWithList(Reader reader)
throws IOException {
TestModelList model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModelList.class);
- assertEquals(Arrays.asList((long) 1, (long) 2, (long) 3), model.arrayField);
+ assertEquals(List.of((long) 1, (long) 2, (long) 3), model.arrayField);
}
static class TestModelList {
@@ -1235,7 +1235,7 @@ private void testMetadata(Reader reader, int ipVersion, long recordSize) {
assertEquals(ipVersion, metadata.getIpVersion());
assertEquals("Test", metadata.getDatabaseType());
- List languages = new ArrayList<>(Arrays.asList("en", "zh"));
+ List languages = new ArrayList<>(List.of("en", "zh"));
assertEquals(languages, metadata.getLanguages());