├── README.dev.md ├── .gitmodules ├── checkstyle-suppressions.xml ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── zizmor.yml │ ├── test.yml │ └── codeql-analysis.yml ├── src ├── main │ └── java │ │ ├── module-info.java │ │ └── com │ │ └── maxmind │ │ └── geoip2 │ │ ├── InetAddressModule.java │ │ ├── exception │ │ ├── AuthenticationException.java │ │ ├── PermissionRequiredException.java │ │ ├── OutOfQueriesException.java │ │ ├── AddressNotFoundException.java │ │ ├── GeoIp2Exception.java │ │ ├── InvalidRequestException.java │ │ └── HttpException.java │ │ ├── InetAddressSerializer.java │ │ ├── GeoIp2Provider.java │ │ ├── InetAddressDeserializer.java │ │ ├── record │ │ ├── MaxMind.java │ │ ├── Postal.java │ │ ├── Anonymizer.java │ │ ├── Continent.java │ │ ├── City.java │ │ ├── Subdivision.java │ │ ├── Country.java │ │ ├── Location.java │ │ └── RepresentedCountry.java │ │ ├── JsonSerializable.java │ │ ├── NamedRecord.java │ │ ├── WebServiceProvider.java │ │ ├── NetworkDeserializer.java │ │ ├── model │ │ ├── DomainResponse.java │ │ ├── AsnResponse.java │ │ ├── AnonymousIpResponse.java │ │ ├── ConnectionTypeResponse.java │ │ ├── IpRiskResponse.java │ │ ├── CountryResponse.java │ │ ├── IspResponse.java │ │ ├── AnonymousPlusResponse.java │ │ ├── InsightsResponse.java │ │ ├── EnterpriseResponse.java │ │ └── CityResponse.java │ │ └── DatabaseProvider.java ├── test │ ├── java │ │ └── com │ │ │ └── maxmind │ │ │ └── geoip2 │ │ │ ├── json │ │ │ └── File.java │ │ │ ├── matchers │ │ │ ├── CodeMatcher.java │ │ │ └── HttpStatusMatcher.java │ │ │ ├── NetworkDeserializerTest.java │ │ │ └── model │ │ │ ├── CityResponseTest.java │ │ │ ├── CountryResponseTest.java │ │ │ └── InsightsResponseTest.java │ └── resources │ │ └── test-data │ │ ├── city0.json │ │ ├── country0.json │ │ ├── insights0.json │ │ └── insights1.json └── assembly │ └── bin.xml ├── sample └── Benchmark.java ├── dev-bin └── release.sh ├── LICENSE └── pom.xml /README.dev.md: -------------------------------------------------------------------------------- 1 | See the [`README.dev.md` in `minfraud-api-java`](https://github.com/maxmind/minfraud-api-java/blob/main/README.dev.md). 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/test/resources/maxmind-db"] 2 | path = src/test/resources/maxmind-db 3 | url = https://github.com/maxmind/MaxMind-DB 4 | -------------------------------------------------------------------------------- /checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *~ 3 | *.iml 4 | *.jar 5 | *.war 6 | *.ear 7 | *.sw? 8 | *.classpath 9 | .claude 10 | .gh-pages 11 | .idea 12 | .pmd 13 | .project 14 | .settings 15 | doc 16 | hs_err*.log 17 | pom.xml.versionsBackup 18 | target 19 | reports 20 | Test.java 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: / 5 | schedule: 6 | interval: daily 7 | time: '14:00' 8 | open-pull-requests-limit: 10 9 | groups: 10 | jackson: 11 | patterns: 12 | - com.fasterxml.jackson* 13 | cooldown: 14 | default-days: 7 15 | - package-ecosystem: github-actions 16 | directory: / 17 | schedule: 18 | interval: daily 19 | time: '14:00' 20 | cooldown: 21 | default-days: 7 22 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | @SuppressWarnings("module") // suppress terminal digit warning 2 | module com.maxmind.geoip2 { 3 | requires com.fasterxml.jackson.annotation; 4 | requires com.fasterxml.jackson.databind; 5 | requires com.fasterxml.jackson.datatype.jsr310; 6 | requires transitive com.maxmind.db; 7 | requires java.net.http; 8 | 9 | exports com.maxmind.geoip2; 10 | exports com.maxmind.geoip2.exception; 11 | exports com.maxmind.geoip2.model; 12 | exports com.maxmind.geoip2.record; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/maxmind/geoip2/json/File.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.json; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.net.URL; 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | 9 | public class File { 10 | public static String readJsonFile(String name) throws IOException, 11 | URISyntaxException { 12 | URL resource = File.class 13 | .getResource("/test-data/" + name + ".json"); 14 | return Files.readString(Paths.get(resource.toURI())); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/zizmor.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Security Analysis with zizmor 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["**"] 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | zizmor: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | security-events: write 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v6 19 | with: 20 | persist-credentials: false 21 | 22 | - name: Run zizmor 23 | uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0 24 | -------------------------------------------------------------------------------- /src/test/resources/test-data/city0.json: -------------------------------------------------------------------------------- 1 | { 2 | "continent": { 3 | "code": "NA", 4 | "geoname_id": 42, 5 | "names": { 6 | "zh-CN": "北美洲", 7 | "en": "North America" 8 | } 9 | }, 10 | "country": { 11 | "confidence": 56, 12 | "geoname_id": 1, 13 | "iso_code": "US", 14 | "names": { 15 | "ru": "объединяет государства", 16 | "en": "United States", 17 | "zh-CN": "美国" 18 | } 19 | }, 20 | "registered_country": { 21 | "geoname_id": 2, 22 | "iso_code": "CA", 23 | "names": { 24 | "en": "Canada" 25 | } 26 | }, 27 | "traits": { 28 | "ip_address": "1.2.3.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/InetAddressModule.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.fasterxml.jackson.databind.module.SimpleModule; 4 | import java.net.InetAddress; 5 | 6 | /** 7 | * Jackson module for InetAddress serialization and deserialization. 8 | */ 9 | public class InetAddressModule extends SimpleModule { 10 | /** 11 | * Constructs an instance of {@code InetAddressModule}. 12 | */ 13 | public InetAddressModule() { 14 | super("InetAddressModule"); 15 | addSerializer(InetAddress.class, new InetAddressSerializer()); 16 | addDeserializer(InetAddress.class, new InetAddressDeserializer()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/exception/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.exception; 2 | 3 | /** 4 | * This exception is thrown when there is an authentication error. 5 | */ 6 | public final class AuthenticationException extends GeoIp2Exception { 7 | 8 | 9 | /** 10 | * @param message A message explaining the cause of the error. 11 | */ 12 | public AuthenticationException(String message) { 13 | super(message); 14 | } 15 | 16 | /** 17 | * @param message A message explaining the cause of the error. 18 | * @param e The cause of the exception. 19 | */ 20 | public AuthenticationException(String message, Throwable e) { 21 | super(message, e); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/exception/PermissionRequiredException.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.exception; 2 | 3 | 4 | /** 5 | * This exception is thrown when permission is required to use the service. 6 | */ 7 | public final class PermissionRequiredException extends GeoIp2Exception { 8 | /** 9 | * @param message A message explaining the cause of the error. 10 | */ 11 | public PermissionRequiredException(String message) { 12 | super(message); 13 | } 14 | 15 | /** 16 | * @param message A message explaining the cause of the error. 17 | * @param e The cause of the exception. 18 | */ 19 | public PermissionRequiredException(String message, Throwable e) { 20 | super(message, e); 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/exception/OutOfQueriesException.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.exception; 2 | 3 | /** 4 | * This exception is thrown when your account does not have any queries 5 | * remaining for the called service. 6 | */ 7 | public final class OutOfQueriesException extends GeoIp2Exception { 8 | 9 | /** 10 | * @param message A message explaining the cause of the error. 11 | */ 12 | public OutOfQueriesException(String message) { 13 | super(message); 14 | } 15 | 16 | /** 17 | * @param message A message explaining the cause of the error. 18 | * @param e The cause of the exception. 19 | */ 20 | public OutOfQueriesException(String message, Throwable e) { 21 | super(message, e); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/resources/test-data/country0.json: -------------------------------------------------------------------------------- 1 | { 2 | "traits": { 3 | "ip_address": "1.2.3.4" 4 | }, 5 | "continent": { 6 | "names": { 7 | "en": "North America" 8 | }, 9 | "geoname_id": 42, 10 | "code": "NA" 11 | }, 12 | "country": { 13 | "geoname_id": 1, 14 | "confidence": 56, 15 | "names": { 16 | "en": "United States" 17 | }, 18 | "iso_code": "US" 19 | }, 20 | "registered_country": { 21 | "geoname_id": 2, 22 | "names": { 23 | "en": "Canada" 24 | }, 25 | "iso_code": "CA" 26 | }, 27 | "represented_country": { 28 | "geoname_id": 4, 29 | "type": "military", 30 | "names": { 31 | "en": "United Kingdom" 32 | }, 33 | "is_in_european_union": true, 34 | "iso_code": "GB" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/exception/AddressNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.exception; 2 | 3 | /** 4 | * This exception is thrown when the IP address is not found in the database. 5 | * This generally means that the address was a private or reserved address. 6 | */ 7 | public final class AddressNotFoundException extends GeoIp2Exception { 8 | 9 | 10 | /** 11 | * @param message A message explaining the cause of the error. 12 | */ 13 | public AddressNotFoundException(String message) { 14 | super(message); 15 | } 16 | 17 | /** 18 | * @param message A message explaining the cause of the error. 19 | * @param e The cause of the exception. 20 | */ 21 | public AddressNotFoundException(String message, Throwable e) { 22 | super(message, e); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: [push, pull_request] 3 | permissions: {} 4 | jobs: 5 | test: 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | distribution: ['zulu'] 11 | os: [ubuntu-latest, windows-latest, macos-latest] 12 | version: [ 17, 21, 24 ] 13 | steps: 14 | - uses: actions/checkout@v6 15 | with: 16 | submodules: true 17 | persist-credentials: false 18 | - uses: actions/setup-java@v5 19 | with: 20 | distribution: ${{ matrix.distribution }} 21 | java-version: ${{ matrix.version }} 22 | - run: mvn test -B 23 | # This is after the test run to work around 24 | # https://issues.apache.org/jira/projects/MJAVADOC/issues/MJAVADOC-736 25 | - run: mvn javadoc:javadoc 26 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/InetAddressSerializer.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.SerializerProvider; 5 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 6 | import java.io.IOException; 7 | import java.net.InetAddress; 8 | 9 | /** 10 | * Serializes InetAddress to its host address string representation. 11 | */ 12 | public class InetAddressSerializer extends StdSerializer { 13 | /** 14 | * Constructs an instance of {@code InetAddressSerializer}. 15 | */ 16 | public InetAddressSerializer() { 17 | super(InetAddress.class); 18 | } 19 | 20 | @Override 21 | public void serialize(InetAddress value, JsonGenerator gen, SerializerProvider provider) 22 | throws IOException { 23 | if (value == null) { 24 | gen.writeNull(); 25 | } else { 26 | gen.writeString(value.getHostAddress()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/exception/GeoIp2Exception.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.exception; 2 | 3 | /** 4 | * This class represents a generic GeoIP2 error. All other exceptions thrown by 5 | * the GeoIP2 API subclass this exception 6 | */ 7 | public sealed class GeoIp2Exception extends Exception 8 | permits AddressNotFoundException, 9 | AuthenticationException, 10 | InvalidRequestException, 11 | OutOfQueriesException, 12 | PermissionRequiredException { 13 | 14 | 15 | /** 16 | * @param message A message describing the reason why the exception was thrown. 17 | */ 18 | public GeoIp2Exception(String message) { 19 | super(message); 20 | } 21 | 22 | /** 23 | * @param message A message describing the reason why the exception was thrown. 24 | * @param cause The cause of the exception. 25 | */ 26 | public GeoIp2Exception(String message, Throwable cause) { 27 | super(message, cause); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/assembly/bin.xml: -------------------------------------------------------------------------------- 1 | 2 | with-dependencies 3 | 4 | zip 5 | 6 | 7 | 8 | false 9 | runtime 10 | lib 11 | 12 | 13 | 14 | 15 | ${project.basedir} 16 | / 17 | 18 | README.md 19 | LICENSE* 20 | CHANGELOG* 21 | 22 | 23 | 24 | ${project.basedir}/target 25 | lib 26 | 27 | *.jar 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/java/com/maxmind/geoip2/matchers/CodeMatcher.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.matchers; 2 | 3 | import com.maxmind.geoip2.exception.InvalidRequestException; 4 | import org.hamcrest.Description; 5 | import org.hamcrest.TypeSafeMatcher; 6 | 7 | public class CodeMatcher extends TypeSafeMatcher { 8 | 9 | private String foundErrorCode; 10 | private final String expectedErrorCode; 11 | 12 | public static CodeMatcher hasCode(String item) { 13 | return new CodeMatcher(item); 14 | } 15 | 16 | private CodeMatcher(String expectedErrorCode) { 17 | this.expectedErrorCode = expectedErrorCode; 18 | } 19 | 20 | @Override 21 | protected boolean matchesSafely(final InvalidRequestException exception) { 22 | this.foundErrorCode = exception.code(); 23 | return this.foundErrorCode.equalsIgnoreCase(this.expectedErrorCode); 24 | } 25 | 26 | @Override 27 | public void describeTo(Description description) { 28 | description.appendValue(this.foundErrorCode) 29 | .appendText(" was not found instead of ") 30 | .appendValue(this.expectedErrorCode); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/GeoIp2Provider.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.maxmind.geoip2.exception.GeoIp2Exception; 4 | import com.maxmind.geoip2.model.CityResponse; 5 | import com.maxmind.geoip2.model.CountryResponse; 6 | import java.io.IOException; 7 | import java.net.InetAddress; 8 | 9 | /** 10 | * Interface for GeoIP2 providers. 11 | */ 12 | public interface GeoIp2Provider { 13 | 14 | /** 15 | * @param ipAddress IPv4 or IPv6 address to lookup. 16 | * @return A Country model for the requested IP address. 17 | * @throws GeoIp2Exception if there is an error looking up the IP 18 | * @throws IOException if there is an IO error 19 | */ 20 | CountryResponse country(InetAddress ipAddress) throws IOException, 21 | GeoIp2Exception; 22 | 23 | /** 24 | * @param ipAddress IPv4 or IPv6 address to lookup. 25 | * @return A City model for the requested IP address. 26 | * @throws GeoIp2Exception if there is an error looking up the IP 27 | * @throws IOException if there is an IO error 28 | */ 29 | CityResponse city(InetAddress ipAddress) throws IOException, 30 | GeoIp2Exception; 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/maxmind/geoip2/matchers/HttpStatusMatcher.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.matchers; 2 | 3 | import com.maxmind.geoip2.exception.HttpException; 4 | import org.hamcrest.Description; 5 | import org.hamcrest.TypeSafeMatcher; 6 | 7 | public class HttpStatusMatcher extends TypeSafeMatcher { 8 | 9 | private int foundStatusCode; 10 | private final int expectedStatusCode; 11 | 12 | public static HttpStatusMatcher hasStatus(int item) { 13 | return new HttpStatusMatcher(item); 14 | } 15 | 16 | private HttpStatusMatcher(int expectedStatusCode) { 17 | this.expectedStatusCode = expectedStatusCode; 18 | } 19 | 20 | @Override 21 | protected boolean matchesSafely(final HttpException exception) { 22 | this.foundStatusCode = exception.httpStatus(); 23 | return this.foundStatusCode == this.expectedStatusCode; 24 | } 25 | 26 | @Override 27 | public void describeTo(Description description) { 28 | description.appendValue(String.valueOf(this.foundStatusCode)) 29 | .appendText(" was not found instead of ") 30 | .appendValue(String.valueOf(this.expectedStatusCode)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/InetAddressDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 6 | import java.io.IOException; 7 | import java.net.InetAddress; 8 | import java.net.UnknownHostException; 9 | 10 | /** 11 | * Deserializes a string to an InetAddress. 12 | */ 13 | public class InetAddressDeserializer extends StdDeserializer { 14 | /** 15 | * Constructs an instance of {@code InetAddressDeserializer}. 16 | */ 17 | public InetAddressDeserializer() { 18 | super(InetAddress.class); 19 | } 20 | 21 | @Override 22 | public InetAddress deserialize(JsonParser p, DeserializationContext ctxt) 23 | throws IOException { 24 | var value = p.getValueAsString(); 25 | if (value == null || value.isEmpty()) { 26 | return null; 27 | } 28 | try { 29 | return InetAddress.getByName(value); 30 | } catch (UnknownHostException e) { 31 | throw new IOException("Invalid IP address: " + value, e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/MaxMind.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.maxmind.db.MaxMindDbParameter; 5 | import com.maxmind.geoip2.JsonSerializable; 6 | 7 | /** 8 | *

9 | * Contains data related to your MaxMind account. 10 | *

11 | * 12 | * @param queriesRemaining The number of remaining queries in your account for the current 13 | * web service. This returns {@code null} when called on a database. 14 | */ 15 | public record MaxMind( 16 | @JsonProperty("queries_remaining") 17 | @MaxMindDbParameter(name = "queries_remaining") 18 | Integer queriesRemaining 19 | ) implements JsonSerializable { 20 | 21 | /** 22 | * Constructs a {@code MaxMind} record. 23 | */ 24 | public MaxMind() { 25 | this(null); 26 | } 27 | 28 | /** 29 | * @return The number of remaining queries in your account for the current 30 | * web service. This returns {@code null} when called on a database. 31 | * @deprecated Use {@link #queriesRemaining()} instead. This method will be removed in 6.0.0. 32 | */ 33 | @Deprecated(since = "5.0.0", forRemoval = true) 34 | @JsonProperty("queries_remaining") 35 | public Integer getQueriesRemaining() { 36 | return queriesRemaining(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/JsonSerializable.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.MapperFeature; 5 | import com.fasterxml.jackson.databind.SerializationFeature; 6 | import com.fasterxml.jackson.databind.json.JsonMapper; 7 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 8 | import java.io.IOException; 9 | 10 | /** 11 | * Interface for classes that can be serialized to JSON. 12 | * Provides default implementation for toJson() method. 13 | */ 14 | public interface JsonSerializable { 15 | 16 | /** 17 | * @return JSON representation of this object. The structure is the same as 18 | * the JSON provided by the GeoIP2 web service. 19 | * @throws IOException if there is an error serializing the object to JSON. 20 | */ 21 | default String toJson() throws IOException { 22 | JsonMapper mapper = JsonMapper.builder() 23 | .disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS) 24 | .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 25 | .addModule(new JavaTimeModule()) 26 | .addModule(new InetAddressModule()) 27 | .serializationInclusion(JsonInclude.Include.NON_NULL) 28 | .serializationInclusion(JsonInclude.Include.NON_EMPTY) 29 | .build(); 30 | 31 | return mapper.writeValueAsString(this); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/NamedRecord.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Interface for record classes that have localized names and GeoName IDs. 10 | * Provides a default implementation for the name() method that returns the name 11 | * in the first available locale. 12 | */ 13 | public interface NamedRecord extends JsonSerializable { 14 | 15 | /** 16 | * @return The GeoName ID for this location. 17 | */ 18 | @JsonProperty("geoname_id") 19 | Long geonameId(); 20 | 21 | /** 22 | * @return A {@link Map} from locale codes to the name in that locale. 23 | */ 24 | @JsonProperty("names") 25 | Map names(); 26 | 27 | /** 28 | * @return The list of locales to use for name lookups. 29 | */ 30 | @JsonIgnore 31 | List locales(); 32 | 33 | /** 34 | * @return The name based on the locales list. Returns the name in the first 35 | * locale for which a name is available. If no name is available in any of the 36 | * specified locales, returns null. 37 | */ 38 | @JsonIgnore 39 | default String name() { 40 | for (var lang : locales()) { 41 | if (names().containsKey(lang)) { 42 | return names().get(lang); 43 | } 44 | } 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/WebServiceProvider.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.maxmind.geoip2.exception.GeoIp2Exception; 4 | import com.maxmind.geoip2.model.CityResponse; 5 | import com.maxmind.geoip2.model.CountryResponse; 6 | import com.maxmind.geoip2.model.InsightsResponse; 7 | import java.io.IOException; 8 | import java.net.InetAddress; 9 | 10 | /** 11 | * Interface for GeoIP2 web service providers. 12 | */ 13 | public interface WebServiceProvider extends GeoIp2Provider { 14 | /** 15 | * @return A Country model for the requesting IP address 16 | * @throws GeoIp2Exception if there is an error from the web service 17 | * @throws IOException if an IO error happens during the request 18 | */ 19 | CountryResponse country() throws IOException, GeoIp2Exception; 20 | 21 | /** 22 | * @return A City model for the requesting IP address 23 | * @throws GeoIp2Exception if there is an error from the web service 24 | * @throws IOException if an IO error happens during the request 25 | */ 26 | CityResponse city() throws IOException, GeoIp2Exception; 27 | 28 | /** 29 | * @return An Insights model for the requesting IP address 30 | * @throws GeoIp2Exception if there is an error from the web service 31 | * @throws IOException if an IO error happens during the request 32 | */ 33 | InsightsResponse insights() throws IOException, GeoIp2Exception; 34 | 35 | /** 36 | * @param ipAddress IPv4 or IPv6 address to lookup. 37 | * @return An Insight model for the requested IP address. 38 | * @throws GeoIp2Exception if there is an error looking up the IP 39 | * @throws IOException if there is an IO error 40 | */ 41 | InsightsResponse insights(InetAddress ipAddress) throws IOException, 42 | GeoIp2Exception; 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'dependabot/**' 7 | pull_request: 8 | schedule: 9 | - cron: '0 6 * * 3' 10 | 11 | jobs: 12 | CodeQL-Build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | security-events: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v6 22 | with: 23 | # We must fetch at least the immediate parents so that if this is 24 | # a pull request then we can checkout the head. 25 | fetch-depth: 2 26 | persist-credentials: false 27 | 28 | # If this run was triggered by a pull request event, then checkout 29 | # the head of the pull request instead of the merge commit. 30 | - run: git checkout HEAD^2 31 | if: ${{ github.event_name == 'pull_request' }} 32 | 33 | # Initializes the CodeQL tools for scanning. 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v4 36 | # Override language selection by uncommenting this and choosing your languages 37 | # with: 38 | # languages: go, javascript, csharp, python, cpp, java 39 | 40 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 41 | # If this step fails, then you should remove it and run the build manually (see below) 42 | - name: Autobuild 43 | uses: github/codeql-action/autobuild@v4 44 | 45 | # ℹ️ Command-line programs to run using the OS shell. 46 | # 📚 https://git.io/JvXDl 47 | 48 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 49 | # and modify them (or add more) to build your code if your project 50 | # uses a compiled language 51 | 52 | #- run: | 53 | # make bootstrap 54 | # make release 55 | 56 | - name: Perform CodeQL Analysis 57 | uses: github/codeql-action/analyze@v4 58 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/Postal.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.maxmind.db.MaxMindDbParameter; 5 | import com.maxmind.geoip2.JsonSerializable; 6 | 7 | /** 8 | *

9 | * Contains data for the postal record associated with an IP address. 10 | *

11 | * 12 | * @param code The postal code of the location. Postal codes are not available for all 13 | * countries. In some countries, this will only contain part of the postal 14 | * code. 15 | * @param confidence A value from 0-100 indicating MaxMind's confidence that the postal 16 | * code is correct. This attribute is only available from the Insights 17 | * web service and the GeoIP2 Enterprise database. 18 | */ 19 | public record Postal( 20 | @JsonProperty("code") 21 | @MaxMindDbParameter(name = "code") 22 | String code, 23 | 24 | @JsonProperty("confidence") 25 | @MaxMindDbParameter(name = "confidence") 26 | Integer confidence 27 | ) implements JsonSerializable { 28 | 29 | /** 30 | * Constructs a {@code Postal} record. 31 | */ 32 | public Postal() { 33 | this(null, null); 34 | } 35 | 36 | /** 37 | * @return The postal code of the location. Postal codes are not available 38 | * for all countries. In some countries, this will only contain part 39 | * of the postal code. 40 | * @deprecated Use {@link #code()} instead. This method will be removed in 6.0.0. 41 | */ 42 | @Deprecated(since = "5.0.0", forRemoval = true) 43 | public String getCode() { 44 | return code(); 45 | } 46 | 47 | /** 48 | * @return A value from 0-100 indicating MaxMind's confidence that the 49 | * postal code is correct. This attribute is only available from the 50 | * Insights web service and the GeoIP2 Enterprise database. 51 | * @deprecated Use {@link #confidence()} instead. This method will be removed in 6.0.0. 52 | */ 53 | @Deprecated(since = "5.0.0", forRemoval = true) 54 | public Integer getConfidence() { 55 | return confidence(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/exception/InvalidRequestException.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.exception; 2 | 3 | import java.net.URI; 4 | 5 | /** 6 | * This class represents a non-specific error returned by MaxMind's GeoIP2 web 7 | * service. This occurs when the web service is up and responding to requests, 8 | * but the request sent was invalid in some way. 9 | */ 10 | public final class InvalidRequestException extends GeoIp2Exception { 11 | private final String code; 12 | private final URI uri; 13 | 14 | /** 15 | * @param message A message explaining the cause of the error. 16 | * @param code The error code returned by the web service. 17 | * @param uri The URI queried. 18 | */ 19 | public InvalidRequestException(String message, String code, URI uri) { 20 | super(message); 21 | this.uri = uri; 22 | this.code = code; 23 | } 24 | 25 | /** 26 | * @param message A message explaining the cause of the error. 27 | * @param code The error code returned by the web service. 28 | * @param httpStatus The HTTP status of the response. 29 | * @param uri The URI queried. 30 | * @param e The cause of the exception. 31 | */ 32 | public InvalidRequestException(String message, String code, int httpStatus, 33 | URI uri, Throwable e) { 34 | super(message, e); 35 | this.code = code; 36 | this.uri = uri; 37 | } 38 | 39 | /** 40 | * @return The error code returned by the MaxMind web service. 41 | */ 42 | public String code() { 43 | return this.code; 44 | } 45 | 46 | /** 47 | * @return The error code returned by the MaxMind web service. 48 | * @deprecated Use {@link #code()} instead. This method will be removed in 6.0.0. 49 | */ 50 | @Deprecated(since = "5.0.0", forRemoval = true) 51 | public String getCode() { 52 | return code(); 53 | } 54 | 55 | /** 56 | * @return the URI queried. 57 | */ 58 | public URI uri() { 59 | return this.uri; 60 | } 61 | 62 | /** 63 | * @return the URI queried. 64 | * @deprecated Use {@link #uri()} instead. This method will be removed in 6.0.0. 65 | */ 66 | @Deprecated(since = "5.0.0", forRemoval = true) 67 | public URI getUri() { 68 | return uri(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/exception/HttpException.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.exception; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | 6 | /** 7 | * This class represents an HTTP transport error. This is not an error returned 8 | * by the web service itself. As such, it is a IOException instead of a 9 | * GeoIp2Exception. 10 | */ 11 | public final class HttpException extends IOException { 12 | private final int httpStatus; 13 | private final URI uri; 14 | 15 | /** 16 | * @param message A message describing the reason why the exception was thrown. 17 | * @param httpStatus The HTTP status of the response that caused the exception. 18 | * @param uri The URI queried. 19 | */ 20 | public HttpException(String message, int httpStatus, URI uri) { 21 | super(message); 22 | this.httpStatus = httpStatus; 23 | this.uri = uri; 24 | } 25 | 26 | /** 27 | * @param message A message describing the reason why the exception was thrown. 28 | * @param httpStatus The HTTP status of the response that caused the exception. 29 | * @param uri The URI queried. 30 | * @param cause The cause of the exception. 31 | */ 32 | public HttpException(String message, int httpStatus, URI uri, 33 | Throwable cause) { 34 | super(message, cause); 35 | this.httpStatus = httpStatus; 36 | this.uri = uri; 37 | } 38 | 39 | /** 40 | * @return the HTTP status of the query that caused the exception. 41 | */ 42 | public int httpStatus() { 43 | return this.httpStatus; 44 | } 45 | 46 | /** 47 | * @return the HTTP status of the query that caused the exception. 48 | * @deprecated Use {@link #httpStatus()} instead. This method will be removed in 6.0.0. 49 | */ 50 | @Deprecated(since = "5.0.0", forRemoval = true) 51 | public int getHttpStatus() { 52 | return httpStatus(); 53 | } 54 | 55 | /** 56 | * @return the URI queried. 57 | */ 58 | public URI uri() { 59 | return this.uri; 60 | } 61 | 62 | /** 63 | * @return the URI queried. 64 | * @deprecated Use {@link #uri()} instead. This method will be removed in 6.0.0. 65 | */ 66 | @Deprecated(since = "5.0.0", forRemoval = true) 67 | public URI getUri() { 68 | return uri(); 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/resources/test-data/insights0.json: -------------------------------------------------------------------------------- 1 | { 2 | "city": { 3 | "confidence": 76, 4 | "geoname_id": 9876, 5 | "names": { 6 | "en": "Minneapolis" 7 | } 8 | }, 9 | "continent": { 10 | "code": "NA", 11 | "geoname_id": 42, 12 | "names": { 13 | "en": "North America" 14 | } 15 | }, 16 | "country": { 17 | "confidence": 99, 18 | "geoname_id": 1, 19 | "iso_code": "US", 20 | "names": { 21 | "en": "United States of America" 22 | } 23 | }, 24 | "location": { 25 | "accuracy_radius": 1500, 26 | "average_income": 24626, 27 | "latitude": 44.98, 28 | "longitude": 93.2636, 29 | "population_density": 1341, 30 | "time_zone": "America/Chicago" 31 | }, 32 | "maxmind": { 33 | "queries_remaining": 11 34 | }, 35 | "postal": { 36 | "code": "55401", 37 | "confidence": 33 38 | }, 39 | "registered_country": { 40 | "geoname_id": 2, 41 | "iso_code": "CA", 42 | "names": { 43 | "en": "Canada" 44 | } 45 | }, 46 | "represented_country": { 47 | "geoname_id": 3, 48 | "is_in_european_union": true, 49 | "iso_code": "GB", 50 | "names": { 51 | "en": "United Kingdom" 52 | }, 53 | "type": "C" 54 | }, 55 | "subdivisions": [ 56 | { 57 | "confidence": 88, 58 | "geoname_id": 574635, 59 | "iso_code": "MN", 60 | "names": { 61 | "en": "Minnesota" 62 | } 63 | }, 64 | { 65 | "iso_code": "TT" 66 | } 67 | ], 68 | "traits": { 69 | "autonomous_system_number": 1234, 70 | "autonomous_system_organization": "AS Organization", 71 | "connection_type": "Cable/DSL", 72 | "domain": "example.com", 73 | "ip_address": "1.2.3.4", 74 | "isp": "Comcast", 75 | "is_anonymous": true, 76 | "is_anonymous_vpn": true, 77 | "is_anycast": true, 78 | "is_hosting_provider": true, 79 | "is_public_proxy": true, 80 | "is_residential_proxy": true, 81 | "is_tor_exit_node": true, 82 | "organization": "Blorg", 83 | "static_ip_score": 1.3, 84 | "user_count": 2, 85 | "user_type": "college", 86 | "ip_risk_snapshot": 0.01 87 | }, 88 | "anonymizer": { 89 | "confidence": 99, 90 | "is_anonymous": true, 91 | "is_anonymous_vpn": true, 92 | "is_hosting_provider": true, 93 | "is_public_proxy": true, 94 | "is_residential_proxy": true, 95 | "is_tor_exit_node": true, 96 | "network_last_seen": "2024-12-31", 97 | "provider_name": "NordVPN" 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/NetworkDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 6 | import com.maxmind.db.Network; 7 | import java.io.IOException; 8 | import java.net.InetAddress; 9 | import java.net.UnknownHostException; 10 | 11 | /** 12 | * This class provides a deserializer for the Network class. 13 | */ 14 | public final class NetworkDeserializer extends StdDeserializer { 15 | 16 | /** 17 | * Constructs a {@code NetworkDeserializer} with no type specified. 18 | */ 19 | public NetworkDeserializer() { 20 | this(null); 21 | } 22 | 23 | /** 24 | * Constructs a @{code NetworkDeserializer} object. 25 | * 26 | * @param vc a class 27 | */ 28 | public NetworkDeserializer(Class vc) { 29 | super(vc); 30 | } 31 | 32 | @Override 33 | public Network deserialize(JsonParser jsonparser, DeserializationContext context) 34 | throws IOException { 35 | 36 | final var cidr = jsonparser.getValueAsString(); 37 | if (cidr == null || cidr.isBlank()) { 38 | return null; 39 | } 40 | return parseCidr(cidr); 41 | } 42 | 43 | private static Network parseCidr(String cidr) throws IOException { 44 | final var parts = cidr.split("/", 2); 45 | if (parts.length != 2) { 46 | throw new IllegalArgumentException("Invalid CIDR format: " + cidr); 47 | } 48 | 49 | final var addrPart = parts[0]; 50 | final var prefixPart = parts[1]; 51 | 52 | final InetAddress address; 53 | try { 54 | address = InetAddress.getByName(addrPart); 55 | } catch (UnknownHostException e) { 56 | throw new IOException("Unknown host in CIDR: " + cidr, e); 57 | } 58 | 59 | final var prefixLength = parsePrefixLength(prefixPart, cidr); 60 | 61 | final var maxPrefix = (address.getAddress().length == 4) ? 32 : 128; 62 | if (prefixLength < 0 || prefixLength > maxPrefix) { 63 | throw new IllegalArgumentException( 64 | "Prefix length out of range (0-" + maxPrefix + ") for CIDR: " + cidr); 65 | } 66 | 67 | return new Network(address, prefixLength); 68 | } 69 | 70 | private static int parsePrefixLength(String prefixPart, String cidr) { 71 | try { 72 | return Integer.parseInt(prefixPart); 73 | } catch (NumberFormatException e) { 74 | throw new IllegalArgumentException( 75 | "Invalid prefix length in CIDR: " + cidr, e); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /sample/Benchmark.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.io.IOException; 3 | import java.net.InetAddress; 4 | import java.net.UnknownHostException; 5 | import java.util.Random; 6 | 7 | import com.maxmind.db.CHMCache; 8 | import com.maxmind.db.NoCache; 9 | import com.maxmind.db.NodeCache; 10 | import com.maxmind.db.Reader.FileMode; 11 | import com.maxmind.geoip2.DatabaseReader; 12 | import com.maxmind.geoip2.exception.AddressNotFoundException; 13 | import com.maxmind.geoip2.exception.GeoIp2Exception; 14 | import com.maxmind.geoip2.model.CityResponse; 15 | 16 | public class Benchmark { 17 | 18 | private final static int COUNT = 1000000; 19 | private final static int WARMUPS = 3; 20 | private final static int BENCHMARKS = 5; 21 | private final static boolean TRACE = false; 22 | 23 | public static void main(String[] args) throws GeoIp2Exception, IOException { 24 | File file = new File(args.length > 0 ? args[0] : "GeoLite2-City.mmdb"); 25 | System.out.println("No caching"); 26 | loop("Warming up", file, WARMUPS, NoCache.getInstance()); 27 | loop("Benchmarking", file, BENCHMARKS, NoCache.getInstance()); 28 | 29 | System.out.println("With caching"); 30 | loop("Warming up", file, WARMUPS, new CHMCache()); 31 | loop("Benchmarking", file, BENCHMARKS, new CHMCache()); 32 | } 33 | 34 | private static void loop(String msg, File file, int loops, NodeCache cache) 35 | throws GeoIp2Exception, IOException { 36 | System.out.println(msg); 37 | for (int i = 0; i < loops; i++) { 38 | DatabaseReader r = 39 | new DatabaseReader.Builder(file).fileMode(FileMode.MEMORY_MAPPED).withCache(cache) 40 | .build(); 41 | bench(r, COUNT, i); 42 | } 43 | System.out.println(); 44 | } 45 | 46 | private static void bench(DatabaseReader r, int count, int seed) 47 | throws GeoIp2Exception, UnknownHostException { 48 | Random random = new Random(seed); 49 | long startTime = System.nanoTime(); 50 | byte[] address = new byte[4]; 51 | for (int i = 0; i < count; i++) { 52 | random.nextBytes(address); 53 | InetAddress ip = InetAddress.getByAddress(address); 54 | CityResponse t; 55 | try { 56 | t = r.city(ip); 57 | } catch (AddressNotFoundException | IOException e) { 58 | } 59 | if (TRACE) { 60 | if (i % 50000 == 0) { 61 | System.out.println(i + " " + ip); 62 | System.out.println(t); 63 | } 64 | } 65 | } 66 | long endTime = System.nanoTime(); 67 | 68 | long duration = endTime - startTime; 69 | long qps = count * 1000000000L / duration; 70 | System.out.println("Requests per second: " + qps); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/DomainResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 7 | import com.maxmind.db.MaxMindDbIpAddress; 8 | import com.maxmind.db.MaxMindDbNetwork; 9 | import com.maxmind.db.MaxMindDbParameter; 10 | import com.maxmind.db.Network; 11 | import com.maxmind.geoip2.JsonSerializable; 12 | import com.maxmind.geoip2.NetworkDeserializer; 13 | import java.net.InetAddress; 14 | 15 | /** 16 | * This class provides the GeoIP2 Domain model. 17 | * 18 | * @param domain The second level domain associated with the IP address. This will be something 19 | * like "example.com" or "example.co.uk", not "foo.example.com". 20 | * @param ipAddress The IP address that the data in the model is for. 21 | * @param network The network associated with the record. In particular, this is the largest 22 | * network where all the fields besides IP address have the same value. 23 | */ 24 | public record DomainResponse( 25 | @JsonProperty("domain") 26 | @MaxMindDbParameter(name = "domain") 27 | String domain, 28 | 29 | @JsonProperty("ip_address") 30 | @MaxMindDbIpAddress 31 | InetAddress ipAddress, 32 | 33 | @JsonProperty("network") 34 | @JsonDeserialize(using = NetworkDeserializer.class) 35 | @MaxMindDbNetwork 36 | Network network 37 | ) implements JsonSerializable { 38 | 39 | /** 40 | * @return The second level domain associated with the IP address. This 41 | * will be something like "example.com" or "example.co.uk", not 42 | * "foo.example.com". 43 | * @deprecated Use {@link #domain()} instead. This method will be removed in 6.0.0. 44 | */ 45 | @Deprecated(since = "5.0.0", forRemoval = true) 46 | public String getDomain() { 47 | return domain(); 48 | } 49 | 50 | /** 51 | * @return The IP address that the data in the model is for. 52 | * @deprecated Use {@link #ipAddress()} instead. This method will be removed in 6.0.0. 53 | */ 54 | @Deprecated(since = "5.0.0", forRemoval = true) 55 | @JsonProperty("ip_address") 56 | public String getIpAddress() { 57 | return ipAddress().getHostAddress(); 58 | } 59 | 60 | /** 61 | * @return The network associated with the record. In particular, this is 62 | * the largest network where all the fields besides IP address have the 63 | * same value. 64 | * @deprecated Use {@link #network()} instead. This method will be removed in 6.0.0. 65 | */ 66 | @Deprecated(since = "5.0.0", forRemoval = true) 67 | @JsonProperty 68 | @JsonSerialize(using = ToStringSerializer.class) 69 | public Network getNetwork() { 70 | return network(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/maxmind/geoip2/NetworkDeserializerTest.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.fasterxml.jackson.core.JsonFactory; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.maxmind.db.Network; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.io.IOException; 9 | import java.net.InetAddress; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | final class NetworkDeserializerTest { 14 | 15 | 16 | private static Network parse(String jsonString) throws IOException { 17 | var deserializer = new NetworkDeserializer(); 18 | JsonFactory jf = new JsonFactory(); 19 | try (JsonParser p = jf.createParser(jsonString)) { 20 | p.nextToken(); 21 | return deserializer.deserialize(p, null); 22 | } 23 | } 24 | private static void assertNetwork(Network n, String addr, int prefix) throws Exception { 25 | assertNotNull(n); 26 | assertEquals(InetAddress.getByName(addr), n.networkAddress()); 27 | assertEquals(prefix, n.prefixLength()); 28 | } 29 | 30 | @Test 31 | void parsesValidIPv4Cidr() throws Exception { 32 | Network actual = parse("\"1.2.3.0/24\""); 33 | assertNetwork(actual, "1.2.3.0", 24); 34 | } 35 | 36 | @Test 37 | void parsesValidIPv6Cidr() throws Exception { 38 | Network actual = parse("\"2001:db8::/32\""); 39 | assertNetwork(actual, "2001:db8::", 32); 40 | } 41 | 42 | @Test 43 | void rejectsWhitespaceInCidr() { 44 | assertThrows(IOException.class, () -> parse("\" 10.0.0.0/8 \"")); 45 | } 46 | 47 | 48 | 49 | 50 | @Test 51 | void returnsNullOnJsonNull() throws Exception { 52 | Network actual = parse("null"); 53 | assertNull(actual); 54 | } 55 | 56 | @Test 57 | void returnsNullOnBlankString() throws Exception { 58 | Network actual = parse("\" \""); 59 | assertNull(actual); 60 | } 61 | 62 | @Test 63 | void throwsOnMissingSlash() { 64 | assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0\"")); 65 | } 66 | 67 | @Test 68 | void throwsOnNonNumericPrefix() { 69 | assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0/xx\"")); 70 | } 71 | 72 | @Test 73 | void throwsOnOutOfRangePrefixIpv4() { 74 | assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0/64\"")); 75 | assertThrows(IllegalArgumentException.class, () -> parse("\"1.2.3.0/-1\"")); 76 | } 77 | 78 | @Test 79 | void throwsOnOutOfRangePrefixIpv6() { 80 | assertThrows(IllegalArgumentException.class, () -> parse("\"::/129\"")); 81 | assertThrows(IllegalArgumentException.class, () -> parse("\"::/-1\"")); 82 | } 83 | 84 | @Test 85 | void wrapsUnknownHostInIOException() { 86 | IOException ex = assertThrows(IOException.class, () -> parse("\"999.999.999.999/24\"")); 87 | assertNotNull(ex.getCause()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/resources/test-data/insights1.json: -------------------------------------------------------------------------------- 1 | { 2 | "city": { 3 | "geoname_id": "2655045", 4 | "names": { 5 | "en": "Boxford" 6 | } 7 | }, 8 | "continent": { 9 | "code": "EU", 10 | "geoname_id": 6255148, 11 | "names": { 12 | "de": "Europa", 13 | "en": "Europe", 14 | "es": "Europa", 15 | "fr": "Europe", 16 | "ja": "ヨーロッパ", 17 | "pt-BR": "Europa", 18 | "ru": "Европа", 19 | "zh-CN": "欧洲" 20 | } 21 | }, 22 | "country": { 23 | "geoname_id": 2635167, 24 | "is_in_european_union": true, 25 | "iso_code": "GB", 26 | "names": { 27 | "de": "Vereinigtes Königreich", 28 | "en": "United Kingdom", 29 | "es": "Reino Unido", 30 | "fr": "Royaume-Uni", 31 | "ja": "イギリス", 32 | "pt-BR": "Reino Unido", 33 | "ru": "Великобритания", 34 | "zh-CN": "英国" 35 | } 36 | }, 37 | "location": { 38 | "accuracy_radius": 100, 39 | "latitude": "51.7500", 40 | "longitude": "-1.2500", 41 | "time_zone": "Europe/London" 42 | }, 43 | "maxmind": { 44 | "queries_remaining": 11 45 | }, 46 | "postal": { 47 | "code": "OX1" 48 | }, 49 | "registered_country": { 50 | "geoname_id": 3017382, 51 | "is_in_european_union": true, 52 | "iso_code": "FR", 53 | "names": { 54 | "de": "Frankreich", 55 | "en": "France", 56 | "es": "Francia", 57 | "fr": "France", 58 | "ja": "フランス共和国", 59 | "pt-BR": "França", 60 | "ru": "Франция", 61 | "zh-CN": "法国" 62 | } 63 | }, 64 | "subdivisions": [ 65 | { 66 | "geoname_id": 6269131, 67 | "iso_code": "ENG", 68 | "names": { 69 | "en": "England", 70 | "es": "Inglaterra", 71 | "fr": "Angleterre", 72 | "pt-BR": "Inglaterra" 73 | } 74 | }, 75 | { 76 | "geoname_id": 3333217, 77 | "iso_code": "WBK", 78 | "names": { 79 | "en": "West Berkshire", 80 | "ru": "Западный Беркшир", 81 | "zh-CN": "西伯克郡" 82 | } 83 | } 84 | ], 85 | "traits": { 86 | "autonomous_system_number": 1234, 87 | "autonomous_system_organization": "AS Organization", 88 | "connection_type": "Cable/DSL", 89 | "domain": "example.com", 90 | "ip_address": "1.2.3.4", 91 | "isp": "Comcast", 92 | "is_anonymous": true, 93 | "is_anonymous_vpn": true, 94 | "is_anycast": true, 95 | "is_hosting_provider": true, 96 | "is_public_proxy": true, 97 | "is_tor_exit_node": true, 98 | "organization": "Blorg", 99 | "static_ip_score": 1.3, 100 | "user_count": 2, 101 | "user_type": "college", 102 | "ip_risk_snapshot": 0.01 103 | }, 104 | "anonymizer": { 105 | "confidence": 99, 106 | "is_anonymous": true, 107 | "is_anonymous_vpn": true, 108 | "is_hosting_provider": true, 109 | "is_public_proxy": true, 110 | "is_residential_proxy": true, 111 | "is_tor_exit_node": true, 112 | "network_last_seen": "2024-12-31", 113 | "provider_name": "NordVPN" 114 | } 115 | } -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/Anonymizer.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.maxmind.geoip2.JsonSerializable; 5 | import java.time.LocalDate; 6 | 7 | /** 8 | *

9 | * Contains data for the anonymizer record associated with an IP address. 10 | *

11 | *

12 | * This record is returned by the GeoIP2 Precision Insights web service. 13 | *

14 | * 15 | * @param confidence A score ranging from 1 to 99 that is our percent confidence that the 16 | * network is currently part of an actively used VPN service. This is only 17 | * available from the GeoIP2 Precision Insights web service. 18 | * @param isAnonymous Whether the IP address belongs to any sort of anonymous network. 19 | * @param isAnonymousVpn Whether the IP address is registered to an anonymous VPN provider. If a 20 | * VPN provider does not register subnets under names associated with them, 21 | * we will likely only flag their IP ranges using isHostingProvider. 22 | * @param isHostingProvider Whether the IP address belongs to a hosting or VPN provider (see 23 | * description of isAnonymousVpn). 24 | * @param isPublicProxy Whether the IP address belongs to a public proxy. 25 | * @param isResidentialProxy Whether the IP address is on a suspected anonymizing network and 26 | * belongs to a residential ISP. 27 | * @param isTorExitNode Whether the IP address is a Tor exit node. 28 | * @param networkLastSeen The last day that the network was sighted in our analysis of anonymized 29 | * networks. This is only available from the GeoIP2 Precision Insights web 30 | * service. 31 | * @param providerName The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated 32 | * with the network. This is only available from the GeoIP2 Precision Insights 33 | * web service. 34 | */ 35 | public record Anonymizer( 36 | @JsonProperty("confidence") 37 | Integer confidence, 38 | 39 | @JsonProperty("is_anonymous") 40 | boolean isAnonymous, 41 | 42 | @JsonProperty("is_anonymous_vpn") 43 | boolean isAnonymousVpn, 44 | 45 | @JsonProperty("is_hosting_provider") 46 | boolean isHostingProvider, 47 | 48 | @JsonProperty("is_public_proxy") 49 | boolean isPublicProxy, 50 | 51 | @JsonProperty("is_residential_proxy") 52 | boolean isResidentialProxy, 53 | 54 | @JsonProperty("is_tor_exit_node") 55 | boolean isTorExitNode, 56 | 57 | @JsonProperty("network_last_seen") 58 | LocalDate networkLastSeen, 59 | 60 | @JsonProperty("provider_name") 61 | String providerName 62 | ) implements JsonSerializable { 63 | 64 | /** 65 | * Constructs an {@code Anonymizer} record with {@code null} values for all the nullable 66 | * fields and {@code false} for all boolean fields. 67 | */ 68 | public Anonymizer() { 69 | this(null, false, false, false, false, false, false, null, null); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/AsnResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 7 | import com.maxmind.db.MaxMindDbIpAddress; 8 | import com.maxmind.db.MaxMindDbNetwork; 9 | import com.maxmind.db.MaxMindDbParameter; 10 | import com.maxmind.db.Network; 11 | import com.maxmind.geoip2.JsonSerializable; 12 | import com.maxmind.geoip2.NetworkDeserializer; 13 | import java.net.InetAddress; 14 | 15 | /** 16 | * This class provides the GeoLite2 ASN model. 17 | * 18 | * @param autonomousSystemNumber The autonomous system number associated with the IP address. 19 | * @param autonomousSystemOrganization The organization associated with the registered autonomous 20 | * system number for the IP address. 21 | * @param ipAddress The IP address that the data in the model is for. 22 | * @param network The network associated with the record. In particular, this is the largest 23 | * network where all the fields besides IP address have the same value. 24 | */ 25 | public record AsnResponse( 26 | @JsonProperty("autonomous_system_number") 27 | @MaxMindDbParameter(name = "autonomous_system_number") 28 | Long autonomousSystemNumber, 29 | 30 | @JsonProperty("autonomous_system_organization") 31 | @MaxMindDbParameter(name = "autonomous_system_organization") 32 | String autonomousSystemOrganization, 33 | 34 | @JsonProperty("ip_address") 35 | @MaxMindDbIpAddress 36 | InetAddress ipAddress, 37 | 38 | @JsonProperty("network") 39 | @JsonDeserialize(using = NetworkDeserializer.class) 40 | @MaxMindDbNetwork 41 | Network network 42 | ) implements JsonSerializable { 43 | 44 | /** 45 | * @return The autonomous system number associated with the IP address. 46 | * @deprecated Use {@link #autonomousSystemNumber()} instead. This method will be removed 47 | * in 6.0.0. 48 | */ 49 | @Deprecated(since = "5.0.0", forRemoval = true) 50 | @JsonProperty("autonomous_system_number") 51 | public Long getAutonomousSystemNumber() { 52 | return autonomousSystemNumber(); 53 | } 54 | 55 | /** 56 | * @return The organization associated with the registered autonomous system 57 | * number for the IP address 58 | * @deprecated Use {@link #autonomousSystemOrganization()} instead. This method will be 59 | * removed in 6.0.0. 60 | */ 61 | @Deprecated(since = "5.0.0", forRemoval = true) 62 | @JsonProperty("autonomous_system_organization") 63 | public String getAutonomousSystemOrganization() { 64 | return autonomousSystemOrganization(); 65 | } 66 | 67 | /** 68 | * @return The IP address that the data in the model is for. 69 | * @deprecated Use {@link #ipAddress()} instead. This method will be removed in 6.0.0. 70 | */ 71 | @Deprecated(since = "5.0.0", forRemoval = true) 72 | @JsonProperty("ip_address") 73 | public String getIpAddress() { 74 | return ipAddress().getHostAddress(); 75 | } 76 | 77 | /** 78 | * @return The network associated with the record. In particular, this is 79 | * the largest network where all the fields besides IP address have the 80 | * same value. 81 | * @deprecated Use {@link #network()} instead. This method will be removed in 6.0.0. 82 | */ 83 | @Deprecated(since = "5.0.0", forRemoval = true) 84 | @JsonProperty 85 | @JsonSerialize(using = ToStringSerializer.class) 86 | public Network getNetwork() { 87 | return network(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/AnonymousIpResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 7 | import com.maxmind.db.MaxMindDbIpAddress; 8 | import com.maxmind.db.MaxMindDbNetwork; 9 | import com.maxmind.db.MaxMindDbParameter; 10 | import com.maxmind.db.Network; 11 | import com.maxmind.geoip2.JsonSerializable; 12 | import com.maxmind.geoip2.NetworkDeserializer; 13 | import java.net.InetAddress; 14 | 15 | /** 16 | * This class provides the GeoIP2 Anonymous IP model. 17 | * 18 | * @param ipAddress The IP address that the data in the model is for. 19 | * @param isAnonymous Whether the IP address belongs to any sort of anonymous network. 20 | * @param isAnonymousVpn Whether the IP address is registered to an anonymous VPN provider. If a 21 | * VPN provider does not register subnets under names associated with them, 22 | * we will likely only flag their IP ranges using isHostingProvider. 23 | * @param isHostingProvider Whether the IP address belongs to a hosting or VPN provider (see 24 | * description of isAnonymousVpn). 25 | * @param isPublicProxy Whether the IP address belongs to a public proxy. 26 | * @param isResidentialProxy Whether the IP address is on a suspected anonymizing network and 27 | * belongs to a residential ISP. 28 | * @param isTorExitNode Whether the IP address is a Tor exit node. 29 | * @param network The network associated with the record. In particular, this is the largest 30 | * network where all the fields besides IP address have the same value. 31 | */ 32 | public record AnonymousIpResponse( 33 | @JsonProperty("ip_address") 34 | @MaxMindDbIpAddress 35 | InetAddress ipAddress, 36 | 37 | @JsonProperty("is_anonymous") 38 | @MaxMindDbParameter(name = "is_anonymous", useDefault = true) 39 | boolean isAnonymous, 40 | 41 | @JsonProperty("is_anonymous_vpn") 42 | @MaxMindDbParameter(name = "is_anonymous_vpn", useDefault = true) 43 | boolean isAnonymousVpn, 44 | 45 | @JsonProperty("is_hosting_provider") 46 | @MaxMindDbParameter(name = "is_hosting_provider", useDefault = true) 47 | boolean isHostingProvider, 48 | 49 | @JsonProperty("is_public_proxy") 50 | @MaxMindDbParameter(name = "is_public_proxy", useDefault = true) 51 | boolean isPublicProxy, 52 | 53 | @JsonProperty("is_residential_proxy") 54 | @MaxMindDbParameter(name = "is_residential_proxy", useDefault = true) 55 | boolean isResidentialProxy, 56 | 57 | @JsonProperty("is_tor_exit_node") 58 | @MaxMindDbParameter(name = "is_tor_exit_node", useDefault = true) 59 | boolean isTorExitNode, 60 | 61 | @JsonProperty("network") 62 | @JsonDeserialize(using = NetworkDeserializer.class) 63 | @MaxMindDbNetwork 64 | Network network 65 | ) implements JsonSerializable { 66 | 67 | /** 68 | * @return The IP address that the data in the model is for. 69 | * @deprecated Use {@link #ipAddress()} instead. This method will be removed in 6.0.0. 70 | */ 71 | @Deprecated(since = "5.0.0", forRemoval = true) 72 | @JsonProperty("ip_address") 73 | public String getIpAddress() { 74 | return ipAddress().getHostAddress(); 75 | } 76 | 77 | /** 78 | * @return The network associated with the record. In particular, this is 79 | * the largest network where all the fields besides IP address have the 80 | * same value. 81 | * @deprecated Use {@link #network()} instead. This method will be removed in 6.0.0. 82 | */ 83 | @Deprecated(since = "5.0.0", forRemoval = true) 84 | @JsonProperty 85 | @JsonSerialize(using = ToStringSerializer.class) 86 | public Network getNetwork() { 87 | return network(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/Continent.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonInject; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.db.MaxMindDbParameter; 6 | import com.maxmind.geoip2.NamedRecord; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | *

12 | * Contains data for the continent record associated with an IP address. 13 | *

14 | *

15 | * Do not use any of the continent names as a database or map key. Use the 16 | * value returned by {@link #geonameId()} or {@link #code()} instead. 17 | *

18 | * 19 | * @param locales The locales to use for retrieving localized names. 20 | * @param code A two character continent code like "NA" (North America) or "OC" 21 | * (Oceania). 22 | * @param geonameId The GeoName ID for the continent. 23 | * @param names A {@link Map} from locale codes to the name in that locale. 24 | */ 25 | public record Continent( 26 | @JacksonInject("locales") 27 | @MaxMindDbParameter(name = "locales") 28 | List locales, 29 | 30 | @JsonProperty("code") 31 | @MaxMindDbParameter(name = "code") 32 | String code, 33 | 34 | @JsonProperty("geoname_id") 35 | @MaxMindDbParameter(name = "geoname_id") 36 | Long geonameId, 37 | 38 | @JsonProperty("names") 39 | @MaxMindDbParameter(name = "names") 40 | Map names 41 | ) implements NamedRecord { 42 | 43 | /** 44 | * Compact canonical constructor that ensures immutability and handles null values. 45 | */ 46 | public Continent { 47 | locales = locales != null ? List.copyOf(locales) : List.of(); 48 | names = names != null ? Map.copyOf(names) : Map.of(); 49 | } 50 | 51 | /** 52 | * Constructs an instance of {@code Continent} with no data. 53 | */ 54 | public Continent() { 55 | this(null, null, null, null); 56 | } 57 | 58 | /** 59 | * Constructs an instance of {@code Continent}. 60 | * 61 | * @param continent The {@code Continent} object to copy. 62 | * @param locales The locales to use. 63 | */ 64 | public Continent( 65 | Continent continent, 66 | List locales 67 | ) { 68 | this( 69 | locales, 70 | continent.code(), 71 | continent.geonameId(), 72 | continent.names() 73 | ); 74 | } 75 | 76 | /** 77 | * @return A two character continent code like "NA" (North America) or "OC" 78 | * (Oceania). 79 | * @deprecated Use {@link #code()} instead. This method will be removed in 6.0.0. 80 | */ 81 | @Deprecated(since = "5.0.0", forRemoval = true) 82 | public String getCode() { 83 | return code(); 84 | } 85 | 86 | /** 87 | * @return The GeoName ID for the continent. 88 | * @deprecated Use {@link #geonameId()} instead. This method will be removed in 6.0.0. 89 | */ 90 | @Deprecated(since = "5.0.0", forRemoval = true) 91 | @JsonProperty("geoname_id") 92 | public Long getGeoNameId() { 93 | return geonameId(); 94 | } 95 | 96 | /** 97 | * @return The name of the continent based on the locales list. 98 | * @deprecated Use {@link #name()} instead. This method will be removed in 6.0.0. 99 | */ 100 | @Deprecated(since = "5.0.0", forRemoval = true) 101 | @com.fasterxml.jackson.annotation.JsonIgnore 102 | public String getName() { 103 | return name(); 104 | } 105 | 106 | /** 107 | * @return A {@link Map} from locale codes to the name in that locale. 108 | * @deprecated Use {@link #names()} instead. This method will be removed in 6.0.0. 109 | */ 110 | @Deprecated(since = "5.0.0", forRemoval = true) 111 | @JsonProperty("names") 112 | public Map getNames() { 113 | return names(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/City.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonInject; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.db.MaxMindDbParameter; 6 | import com.maxmind.geoip2.NamedRecord; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | *

12 | * City-level data associated with an IP address. 13 | *

14 | *

15 | * Do not use any of the city names as a database or map key. Use the value 16 | * returned by {@link #geonameId()} instead. 17 | *

18 | * 19 | * @param locales The locales to use for retrieving localized names. 20 | * @param confidence A value from 0-100 indicating MaxMind's confidence that the city 21 | * is correct. This attribute is only available from the Insights 22 | * web service and the GeoIP2 Enterprise database. 23 | * @param geonameId The GeoName ID for the city. 24 | * @param names A {@link Map} from locale codes to the name in that locale. 25 | */ 26 | public record City( 27 | @JacksonInject("locales") 28 | @MaxMindDbParameter(name = "locales") 29 | List locales, 30 | 31 | @JsonProperty("confidence") 32 | @MaxMindDbParameter(name = "confidence") 33 | Integer confidence, 34 | 35 | @JsonProperty("geoname_id") 36 | @MaxMindDbParameter(name = "geoname_id") 37 | Long geonameId, 38 | 39 | @JsonProperty("names") 40 | @MaxMindDbParameter(name = "names") 41 | Map names 42 | ) implements NamedRecord { 43 | 44 | /** 45 | * Compact canonical constructor that ensures immutability and handles null values. 46 | */ 47 | public City { 48 | locales = locales != null ? List.copyOf(locales) : List.of(); 49 | names = names != null ? Map.copyOf(names) : Map.of(); 50 | } 51 | 52 | /** 53 | * Constructs an instance of {@code City} with no data. 54 | */ 55 | public City() { 56 | this(null, null, null, null); 57 | } 58 | 59 | /** 60 | * Constructs an instance of {@code City}. 61 | * 62 | * @param city The {@code City} object to copy. 63 | * @param locales The locales to use. 64 | */ 65 | public City( 66 | City city, 67 | List locales 68 | ) { 69 | this( 70 | locales, 71 | city.confidence(), 72 | city.geonameId(), 73 | city.names() 74 | ); 75 | } 76 | 77 | /** 78 | * @return A value from 0-100 indicating MaxMind's confidence that the city 79 | * is correct. This attribute is only available from the Insights 80 | * web service and the GeoIP2 Enterprise database. 81 | * @deprecated Use {@link #confidence()} instead. This method will be removed in 6.0.0. 82 | */ 83 | @Deprecated(since = "5.0.0", forRemoval = true) 84 | public Integer getConfidence() { 85 | return confidence(); 86 | } 87 | 88 | /** 89 | * @return The GeoName ID for the city. 90 | * @deprecated Use {@link #geonameId()} instead. This method will be removed in 6.0.0. 91 | */ 92 | @Deprecated(since = "5.0.0", forRemoval = true) 93 | @JsonProperty("geoname_id") 94 | public Long getGeoNameId() { 95 | return geonameId(); 96 | } 97 | 98 | /** 99 | * @return The name of the city based on the locales list passed to the 100 | * constructor. 101 | * @deprecated Use {@link #name()} instead. This method will be removed in 6.0.0. 102 | */ 103 | @Deprecated(since = "5.0.0", forRemoval = true) 104 | @com.fasterxml.jackson.annotation.JsonIgnore 105 | public String getName() { 106 | return name(); 107 | } 108 | 109 | /** 110 | * @return A {@link Map} from locale codes to the name in that locale. 111 | * @deprecated Use {@link #names()} instead. This method will be removed in 6.0.0. 112 | */ 113 | @Deprecated(since = "5.0.0", forRemoval = true) 114 | @JsonProperty("names") 115 | public Map getNames() { 116 | return names(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/ConnectionTypeResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonValue; 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 8 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 9 | import com.maxmind.db.MaxMindDbCreator; 10 | import com.maxmind.db.MaxMindDbIpAddress; 11 | import com.maxmind.db.MaxMindDbNetwork; 12 | import com.maxmind.db.MaxMindDbParameter; 13 | import com.maxmind.db.Network; 14 | import com.maxmind.geoip2.JsonSerializable; 15 | import com.maxmind.geoip2.NetworkDeserializer; 16 | import java.net.InetAddress; 17 | 18 | /** 19 | * This class provides the GeoIP2 Connection-Type model. 20 | * 21 | * @param connectionType The connection type of the IP address. 22 | * @param ipAddress The IP address that the data in the model is for. 23 | * @param network The network associated with the record. In particular, this is the largest 24 | * network where all the fields besides IP address have the same value. 25 | */ 26 | public record ConnectionTypeResponse( 27 | @JsonProperty("connection_type") 28 | @MaxMindDbParameter(name = "connection_type") 29 | ConnectionType connectionType, 30 | 31 | @JsonProperty("ip_address") 32 | @MaxMindDbIpAddress 33 | InetAddress ipAddress, 34 | 35 | @JsonProperty("network") 36 | @JsonDeserialize(using = NetworkDeserializer.class) 37 | @MaxMindDbNetwork 38 | Network network 39 | ) implements JsonSerializable { 40 | 41 | /** 42 | * The enumerated values that connection-type may take. 43 | */ 44 | public enum ConnectionType { 45 | DIALUP("Dialup"), 46 | CABLE_DSL("Cable/DSL"), 47 | CORPORATE("Corporate"), 48 | CELLULAR("Cellular"), 49 | SATELLITE("Satellite"); 50 | 51 | private final String name; 52 | 53 | ConnectionType(String name) { 54 | this.name = name; 55 | } 56 | 57 | /* 58 | * (non-Javadoc) 59 | * 60 | * @see java.lang.Enum#toString() 61 | */ 62 | @JsonValue 63 | @Override 64 | public String toString() { 65 | return this.name; 66 | } 67 | 68 | /** 69 | * Creates an instance of {@code ConnectionTypeResponse} from a string. 70 | * 71 | * @param s The string to create the instance from. 72 | */ 73 | @JsonCreator 74 | @MaxMindDbCreator 75 | public static ConnectionType fromString(String s) { 76 | if (s == null) { 77 | return null; 78 | } 79 | 80 | return switch (s) { 81 | case "Dialup" -> ConnectionType.DIALUP; 82 | case "Cable/DSL" -> ConnectionType.CABLE_DSL; 83 | case "Corporate" -> ConnectionType.CORPORATE; 84 | case "Cellular" -> ConnectionType.CELLULAR; 85 | case "Satellite" -> ConnectionType.SATELLITE; 86 | default -> null; 87 | }; 88 | } 89 | } 90 | 91 | /** 92 | * @return The connection type of the IP address. 93 | * @deprecated Use {@link #connectionType()} instead. This method will be removed in 6.0.0. 94 | */ 95 | @Deprecated(since = "5.0.0", forRemoval = true) 96 | @JsonProperty("connection_type") 97 | public ConnectionType getConnectionType() { 98 | return connectionType(); 99 | } 100 | 101 | /** 102 | * @return The IP address that the data in the model is for. 103 | * @deprecated Use {@link #ipAddress()} instead. This method will be removed in 6.0.0. 104 | */ 105 | @Deprecated(since = "5.0.0", forRemoval = true) 106 | @JsonProperty("ip_address") 107 | public String getIpAddress() { 108 | return ipAddress().getHostAddress(); 109 | } 110 | 111 | /** 112 | * @return The network associated with the record. In particular, this is 113 | * the largest network where all the fields besides IP address have the 114 | * same value. 115 | * @deprecated Use {@link #network()} instead. This method will be removed in 6.0.0. 116 | */ 117 | @Deprecated(since = "5.0.0", forRemoval = true) 118 | @JsonProperty 119 | @JsonSerialize(using = ToStringSerializer.class) 120 | public Network getNetwork() { 121 | return network(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /dev-bin/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu -o pipefail 4 | 5 | # Pre-flight checks - verify all required tools are available and configured 6 | # before making any changes to the repository 7 | 8 | check_command() { 9 | if ! command -v "$1" &>/dev/null; then 10 | echo "Error: $1 is not installed or not in PATH" 11 | exit 1 12 | fi 13 | } 14 | 15 | # Verify gh CLI is authenticated 16 | if ! gh auth status &>/dev/null; then 17 | echo "Error: gh CLI is not authenticated. Run 'gh auth login' first." 18 | exit 1 19 | fi 20 | 21 | # Verify we can access this repository via gh 22 | if ! gh repo view --json name &>/dev/null; then 23 | echo "Error: Cannot access repository via gh. Check your authentication and repository access." 24 | exit 1 25 | fi 26 | 27 | # Verify git can connect to the remote (catches SSH key issues, etc.) 28 | if ! git ls-remote origin &>/dev/null; then 29 | echo "Error: Cannot connect to git remote. Check your git credentials/SSH keys." 30 | exit 1 31 | fi 32 | 33 | check_command perl 34 | check_command mvn 35 | 36 | # Check that we're not on the main branch 37 | current_branch=$(git branch --show-current) 38 | if [ "$current_branch" = "main" ]; then 39 | echo "Error: Releases should not be done directly on the main branch." 40 | echo "Please create a release branch and run this script from there." 41 | exit 1 42 | fi 43 | 44 | # Fetch latest changes and check that we're not behind origin/main 45 | echo "Fetching from origin..." 46 | git fetch origin 47 | 48 | if ! git merge-base --is-ancestor origin/main HEAD; then 49 | echo "Error: Current branch is behind origin/main." 50 | echo "Please merge or rebase with origin/main before releasing." 51 | exit 1 52 | fi 53 | 54 | changelog=$(cat CHANGELOG.md) 55 | 56 | regex=' 57 | ([0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9\-]*) \(([0-9]{4}-[0-9]{2}-[0-9]{2})\) 58 | -* 59 | 60 | ((.| 61 | )*) 62 | ' 63 | 64 | if [[ ! $changelog =~ $regex ]]; then 65 | echo "Could not find date line in change log!" 66 | exit 1 67 | fi 68 | 69 | version="${BASH_REMATCH[1]}" 70 | date="${BASH_REMATCH[2]}" 71 | notes="$(echo "${BASH_REMATCH[3]}" | sed -n -e '/^[0-9]\+\.[0-9]\+\.[0-9]\+/,$!p')" 72 | 73 | if [[ "$date" != "$(date +"%Y-%m-%d")" ]]; then 74 | echo "$date is not today!" 75 | exit 1 76 | fi 77 | 78 | tag="v$version" 79 | 80 | if [ -n "$(git status --porcelain)" ]; then 81 | echo ". is not clean." >&2 82 | exit 1 83 | fi 84 | 85 | if [ ! -d .gh-pages ]; then 86 | echo "Checking out gh-pages in .gh-pages" 87 | git clone -b gh-pages git@github.com:maxmind/GeoIP2-java.git .gh-pages 88 | pushd .gh-pages 89 | else 90 | echo "Updating .gh-pages" 91 | pushd .gh-pages 92 | git pull 93 | fi 94 | 95 | if [ -n "$(git status --porcelain)" ]; then 96 | echo ".gh-pages is not clean" >&2 97 | exit 1 98 | fi 99 | 100 | popd 101 | 102 | mvn versions:display-plugin-updates 103 | mvn versions:display-dependency-updates 104 | 105 | read -r -n 1 -p "Continue given above dependencies? (y/n) " should_continue 106 | 107 | if [ "$should_continue" != "y" ]; then 108 | echo "Aborting" 109 | exit 1 110 | fi 111 | 112 | mvn test 113 | 114 | read -r -n 1 -p "Continue given above tests? (y/n) " should_continue 115 | 116 | if [ "$should_continue" != "y" ]; then 117 | echo "Aborting" 118 | exit 1 119 | fi 120 | 121 | page=.gh-pages/index.md 122 | cat <$page 123 | --- 124 | layout: default 125 | title: MaxMind GeoIP2 Java API 126 | language: java 127 | version: $tag 128 | --- 129 | 130 | EOF 131 | 132 | mvn versions:set -DnewVersion="$version" 133 | 134 | perl -pi -e "s/(?<=)[^<]*/$version/" README.md 135 | perl -pi -e "s/(?<=com\.maxmind\.geoip2\:geoip2\:)\d+\.\d+\.\d+([\w\-]+)?/$version/" README.md 136 | 137 | cat README.md >>$page 138 | 139 | git diff 140 | 141 | read -r -n 1 -p "Commit changes? " should_commit 142 | if [ "$should_commit" != "y" ]; then 143 | echo "Aborting" 144 | exit 1 145 | fi 146 | git add README.md pom.xml 147 | git commit -m "Preparing for $version" 148 | 149 | mvn clean deploy 150 | 151 | rm -fr ".gh-pages/doc/$tag" 152 | cp -r target/reports/apidocs ".gh-pages/doc/$tag" 153 | rm .gh-pages/doc/latest 154 | ln -fs "$tag" .gh-pages/doc/latest 155 | 156 | pushd .gh-pages 157 | 158 | git add doc/ 159 | git commit -m "Updated for $tag" -a 160 | 161 | echo "Release notes for $version: 162 | 163 | $notes 164 | 165 | " 166 | read -r -n 1 -p "Push to origin? " should_push 167 | 168 | if [ "$should_push" != "y" ]; then 169 | echo "Aborting" 170 | exit 1 171 | fi 172 | 173 | git push 174 | 175 | popd 176 | 177 | git push 178 | 179 | gh release create --target "$(git branch --show-current)" -t "$version" -n "$notes" "$tag" \ 180 | "target/geoip2-$version-with-dependencies.zip" \ 181 | "target/geoip2-$version-with-dependencies.zip.asc" 182 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/IpRiskResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 7 | import com.maxmind.db.MaxMindDbIpAddress; 8 | import com.maxmind.db.MaxMindDbNetwork; 9 | import com.maxmind.db.MaxMindDbParameter; 10 | import com.maxmind.db.Network; 11 | import com.maxmind.geoip2.JsonSerializable; 12 | import com.maxmind.geoip2.NetworkDeserializer; 13 | import java.net.InetAddress; 14 | 15 | /** 16 | * This class provides the GeoIP2 IP Risk model. 17 | * 18 | * @param ipAddress The IP address that the data in the model is for. 19 | * @param isAnonymous Whether the IP address belongs to any sort of anonymous network. 20 | * @param isAnonymousVpn Whether the IP address is registered to an anonymous VPN provider. If a 21 | * VPN provider does not register subnets under names associated with them, 22 | * we will likely only flag their IP ranges using isHostingProvider. 23 | * @param isHostingProvider Whether the IP address belongs to a hosting or VPN provider (see 24 | * description of isAnonymousVpn). 25 | * @param isPublicProxy Whether the IP address belongs to a public proxy. 26 | * @param isResidentialProxy Whether the IP address is on a suspected anonymizing network and 27 | * belongs to a residential ISP. 28 | * @param isTorExitNode Whether the IP address is a Tor exit node. 29 | * @param network The network associated with the record. In particular, this is the largest 30 | * network where all the fields besides IP address have the same value. 31 | * @param ipRisk The IP risk score for the IP address. A value of 0.0 indicates that the 32 | * risk score was not set in the database. This is a limitation of primitive 33 | * types in Java - the value cannot be null. In a future major version, this 34 | * field may be changed to a nullable {@code Double} to distinguish between 35 | * "no data" and "zero risk". 36 | */ 37 | // TODO: In the next major version (6.0.0), consider changing ipRisk to Double 38 | // to allow null values, distinguishing "no data" from "zero risk". 39 | public record IpRiskResponse( 40 | @JsonProperty("ip_address") 41 | @MaxMindDbIpAddress 42 | InetAddress ipAddress, 43 | 44 | @JsonProperty("is_anonymous") 45 | @MaxMindDbParameter(name = "is_anonymous", useDefault = true) 46 | boolean isAnonymous, 47 | 48 | @JsonProperty("is_anonymous_vpn") 49 | @MaxMindDbParameter(name = "is_anonymous_vpn", useDefault = true) 50 | boolean isAnonymousVpn, 51 | 52 | @JsonProperty("is_hosting_provider") 53 | @MaxMindDbParameter(name = "is_hosting_provider", useDefault = true) 54 | boolean isHostingProvider, 55 | 56 | @JsonProperty("is_public_proxy") 57 | @MaxMindDbParameter(name = "is_public_proxy", useDefault = true) 58 | boolean isPublicProxy, 59 | 60 | @JsonProperty("is_residential_proxy") 61 | @MaxMindDbParameter(name = "is_residential_proxy", useDefault = true) 62 | boolean isResidentialProxy, 63 | 64 | @JsonProperty("is_tor_exit_node") 65 | @MaxMindDbParameter(name = "is_tor_exit_node", useDefault = true) 66 | boolean isTorExitNode, 67 | 68 | @JsonProperty("network") 69 | @MaxMindDbNetwork 70 | @JsonDeserialize(using = NetworkDeserializer.class) 71 | Network network, 72 | 73 | @JsonProperty("ip_risk") 74 | @MaxMindDbParameter(name = "ip_risk", useDefault = true) 75 | double ipRisk 76 | ) implements JsonSerializable { 77 | 78 | /** 79 | * @return The IP address that the data in the model is for. 80 | * @deprecated Use {@link #ipAddress()} instead. This method will be removed in 6.0.0. 81 | */ 82 | @Deprecated(since = "5.0.0", forRemoval = true) 83 | @JsonProperty("ip_address") 84 | public String getIpAddress() { 85 | return ipAddress().getHostAddress(); 86 | } 87 | 88 | /** 89 | * @return The network associated with the record. In particular, this is 90 | * the largest network where all the fields besides IP address have the 91 | * same value. 92 | * @deprecated Use {@link #network()} instead. This method will be removed in 6.0.0. 93 | */ 94 | @Deprecated(since = "5.0.0", forRemoval = true) 95 | @JsonProperty 96 | @JsonSerialize(using = ToStringSerializer.class) 97 | public Network getNetwork() { 98 | return network(); 99 | } 100 | 101 | /** 102 | * @return The IP risk of a model. 103 | * @deprecated Use {@link #ipRisk()} instead. This method will be removed in 6.0.0. 104 | */ 105 | @Deprecated(since = "5.0.0", forRemoval = true) 106 | @JsonProperty("ip_risk") 107 | public double getIpRisk() { 108 | return ipRisk(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/Subdivision.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonInject; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.db.MaxMindDbParameter; 6 | import com.maxmind.geoip2.NamedRecord; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | *

12 | * Contains data for the subdivisions associated with an IP address. 13 | *

14 | *

15 | * Do not use any of the subdivision names as a database or map key. Use the 16 | * value returned by {@link #geonameId()} or {@link #isoCode()} instead. 17 | *

18 | * 19 | * @param locales The locales to use for retrieving localized names. 20 | * @param confidence A value from 0-100 indicating MaxMind's confidence that the 21 | * subdivision is correct. This attribute is only available from 22 | * the Insights web service and the GeoIP2 Enterprise database. 23 | * @param geonameId The GeoName ID for the subdivision. 24 | * @param isoCode A string up to three characters long containing the subdivision 25 | * portion of the ISO 26 | * 3166-2 code. 27 | * @param names A {@link Map} from locale codes to the name in that locale. 28 | */ 29 | public record Subdivision( 30 | @JacksonInject("locales") 31 | @MaxMindDbParameter(name = "locales") 32 | List locales, 33 | 34 | @JsonProperty("confidence") 35 | @MaxMindDbParameter(name = "confidence") 36 | Integer confidence, 37 | 38 | @JsonProperty("geoname_id") 39 | @MaxMindDbParameter(name = "geoname_id") 40 | Long geonameId, 41 | 42 | @JsonProperty("iso_code") 43 | @MaxMindDbParameter(name = "iso_code") 44 | String isoCode, 45 | 46 | @JsonProperty("names") 47 | @MaxMindDbParameter(name = "names") 48 | Map names 49 | ) implements NamedRecord { 50 | 51 | /** 52 | * Compact canonical constructor that ensures immutability and handles null values. 53 | */ 54 | public Subdivision { 55 | locales = locales != null ? List.copyOf(locales) : List.of(); 56 | names = names != null ? Map.copyOf(names) : Map.of(); 57 | } 58 | 59 | /** 60 | * Constructs a {@code Subdivision} record. 61 | */ 62 | public Subdivision() { 63 | this(null, null, null, null, null); 64 | } 65 | 66 | /** 67 | * Constructs an instance of {@code Subdivision} with the specified parameters. 68 | * 69 | * @param subdivision The {@code Subdivision} object to copy. 70 | * @param locales The locales to use. 71 | */ 72 | public Subdivision( 73 | Subdivision subdivision, 74 | List locales 75 | ) { 76 | this( 77 | locales, 78 | subdivision.confidence(), 79 | subdivision.geonameId(), 80 | subdivision.isoCode(), 81 | subdivision.names() 82 | ); 83 | } 84 | 85 | /** 86 | * @return A value from 0-100 indicating MaxMind's confidence that 87 | * the subdivision is correct. This attribute is only available from 88 | * the Insights web service and the GeoIP2 Enterprise database. 89 | * @deprecated Use {@link #confidence()} instead. This method will be removed in 6.0.0. 90 | */ 91 | @Deprecated(since = "5.0.0", forRemoval = true) 92 | @JsonProperty("confidence") 93 | public Integer getConfidence() { 94 | return confidence(); 95 | } 96 | 97 | /** 98 | * @return A string up to three characters long containing the 99 | * subdivision portion of the ISO 101 | * 3166-2 code. 102 | * @deprecated Use {@link #isoCode()} instead. This method will be removed in 6.0.0. 103 | */ 104 | @Deprecated(since = "5.0.0", forRemoval = true) 105 | @JsonProperty("iso_code") 106 | public String getIsoCode() { 107 | return isoCode(); 108 | } 109 | 110 | /** 111 | * @return The GeoName ID for the subdivision. 112 | * @deprecated Use {@link #geonameId()} instead. This method will be removed in 6.0.0. 113 | */ 114 | @Deprecated(since = "5.0.0", forRemoval = true) 115 | @JsonProperty("geoname_id") 116 | public Long getGeoNameId() { 117 | return geonameId(); 118 | } 119 | 120 | /** 121 | * @return The name of the subdivision based on the locales list. 122 | * @deprecated Use {@link #name()} instead. This method will be removed in 6.0.0. 123 | */ 124 | @Deprecated(since = "5.0.0", forRemoval = true) 125 | @com.fasterxml.jackson.annotation.JsonIgnore 126 | public String getName() { 127 | return name(); 128 | } 129 | 130 | /** 131 | * @return A {@link Map} from locale codes to the name in that locale. 132 | * @deprecated Use {@link #names()} instead. This method will be removed in 6.0.0. 133 | */ 134 | @Deprecated(since = "5.0.0", forRemoval = true) 135 | @JsonProperty("names") 136 | public Map getNames() { 137 | return names(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/Country.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonInject; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.db.MaxMindDbParameter; 6 | import com.maxmind.geoip2.NamedRecord; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | *

12 | * Contains data for the country record associated with an IP address. 13 | *

14 | *

15 | * Do not use any of the country names as a database or map key. Use the value 16 | * returned by {@link #geonameId()} or {@link #isoCode()} instead. 17 | *

18 | * 19 | * @param locales The locales to use for retrieving localized names. 20 | * @param confidence A value from 0-100 indicating MaxMind's confidence that the 21 | * country is correct. This attribute is only available from the 22 | * Insights web service and the GeoIP2 Enterprise database. 23 | * @param geonameId The GeoName ID for the country. 24 | * @param isInEuropeanUnion This is true if the country is a member state of the 25 | * European Union. 26 | * @param isoCode The two-character ISO 27 | * 3166-1 alpha code for the country. 28 | * @param names A {@link Map} from locale codes to the name in that locale. 29 | */ 30 | public record Country( 31 | @JacksonInject("locales") 32 | @MaxMindDbParameter(name = "locales") 33 | List locales, 34 | 35 | @JsonProperty("confidence") 36 | @MaxMindDbParameter(name = "confidence") 37 | Integer confidence, 38 | 39 | @JsonProperty("geoname_id") 40 | @MaxMindDbParameter(name = "geoname_id") 41 | Long geonameId, 42 | 43 | @JsonProperty("is_in_european_union") 44 | @MaxMindDbParameter(name = "is_in_european_union", useDefault = true) 45 | boolean isInEuropeanUnion, 46 | 47 | @JsonProperty("iso_code") 48 | @MaxMindDbParameter(name = "iso_code") 49 | String isoCode, 50 | 51 | @JsonProperty("names") 52 | @MaxMindDbParameter(name = "names") 53 | Map names 54 | ) implements NamedRecord { 55 | 56 | /** 57 | * Compact canonical constructor that ensures immutability and handles null values. 58 | */ 59 | public Country { 60 | locales = locales != null ? List.copyOf(locales) : List.of(); 61 | names = names != null ? Map.copyOf(names) : Map.of(); 62 | } 63 | 64 | /** 65 | * Constructs an instance of {@code Country} with no data. 66 | */ 67 | public Country() { 68 | this(null, null, null, false, null, null); 69 | } 70 | 71 | /** 72 | * Constructs an instance of {@code Country}. 73 | * 74 | * @param country The {@code Country} object to copy. 75 | * @param locales The locales to use. 76 | */ 77 | public Country( 78 | Country country, 79 | List locales 80 | ) { 81 | this( 82 | locales, 83 | country.confidence(), 84 | country.geonameId(), 85 | country.isInEuropeanUnion(), 86 | country.isoCode(), 87 | country.names() 88 | ); 89 | } 90 | 91 | /** 92 | * @return A value from 0-100 indicating MaxMind's confidence that the 93 | * country is correct. This attribute is only available from the 94 | * Insights web service and the GeoIP2 Enterprise database. 95 | * @deprecated Use {@link #confidence()} instead. This method will be removed in 6.0.0. 96 | */ 97 | @Deprecated(since = "5.0.0", forRemoval = true) 98 | public Integer getConfidence() { 99 | return confidence(); 100 | } 101 | 102 | /** 103 | * @return The two-character ISO 105 | * 3166-1 alpha code for the country. 106 | * @deprecated Use {@link #isoCode()} instead. This method will be removed in 6.0.0. 107 | */ 108 | @Deprecated(since = "5.0.0", forRemoval = true) 109 | @JsonProperty("iso_code") 110 | public String getIsoCode() { 111 | return isoCode(); 112 | } 113 | 114 | /** 115 | * @return The GeoName ID for the country. 116 | * @deprecated Use {@link #geonameId()} instead. This method will be removed in 6.0.0. 117 | */ 118 | @Deprecated(since = "5.0.0", forRemoval = true) 119 | @JsonProperty("geoname_id") 120 | public Long getGeoNameId() { 121 | return geonameId(); 122 | } 123 | 124 | /** 125 | * @return The name of the country based on the locales list. 126 | * @deprecated Use {@link #name()} instead. This method will be removed in 6.0.0. 127 | */ 128 | @Deprecated(since = "5.0.0", forRemoval = true) 129 | @com.fasterxml.jackson.annotation.JsonIgnore 130 | public String getName() { 131 | return name(); 132 | } 133 | 134 | /** 135 | * @return A {@link Map} from locale codes to the name in that locale. 136 | * @deprecated Use {@link #names()} instead. This method will be removed in 6.0.0. 137 | */ 138 | @Deprecated(since = "5.0.0", forRemoval = true) 139 | @JsonProperty("names") 140 | public Map getNames() { 141 | return names(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/Location.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.maxmind.db.MaxMindDbParameter; 5 | import com.maxmind.geoip2.JsonSerializable; 6 | 7 | /** 8 | *

9 | * Contains data for the location record associated with an IP address. 10 | *

11 | * 12 | * @param accuracyRadius The approximate accuracy radius in kilometers around the 13 | * latitude and longitude for the IP address. This is the radius 14 | * where we have a 67% confidence that the device using the IP 15 | * address resides within the circle centered at the latitude and 16 | * longitude with the provided radius. 17 | * @param averageIncome The average income in US dollars associated with the requested 18 | * IP address. This attribute is only available from the Insights 19 | * web service. 20 | * @param latitude The approximate latitude of the location associated with the IP 21 | * address. This value is not precise and should not be used to identify 22 | * a particular address or household. 23 | * @param longitude The approximate longitude of the location associated with the IP 24 | * address. This value is not precise and should not be used to identify 25 | * a particular address or household. 26 | * @param populationDensity The estimated population per square kilometer associated with 27 | * the IP address. This attribute is only available from the 28 | * Insights web service. 29 | * @param timeZone The time zone associated with location, as specified by the 30 | * IANA Time Zone Database, 31 | * e.g., "America/New_York". 32 | */ 33 | public record Location( 34 | @JsonProperty("accuracy_radius") 35 | @MaxMindDbParameter(name = "accuracy_radius") 36 | Integer accuracyRadius, 37 | 38 | @JsonProperty("average_income") 39 | @MaxMindDbParameter(name = "average_income") 40 | Integer averageIncome, 41 | 42 | @JsonProperty("latitude") 43 | @MaxMindDbParameter(name = "latitude") 44 | Double latitude, 45 | 46 | @JsonProperty("longitude") 47 | @MaxMindDbParameter(name = "longitude") 48 | Double longitude, 49 | 50 | @JsonProperty("population_density") 51 | @MaxMindDbParameter(name = "population_density") 52 | Integer populationDensity, 53 | 54 | @JsonProperty("time_zone") 55 | @MaxMindDbParameter(name = "time_zone") 56 | String timeZone 57 | ) implements JsonSerializable { 58 | 59 | /** 60 | * Constructs a {@code Location} record with {@code null} values for all the fields. 61 | */ 62 | public Location() { 63 | this(null, null, null, null, null, null); 64 | } 65 | 66 | /** 67 | * @return The average income in US dollars associated with the requested 68 | * IP address. This attribute is only available from the Insights web 69 | * service. 70 | * @deprecated Use {@link #averageIncome()} instead. This method will be removed in 6.0.0. 71 | */ 72 | @Deprecated(since = "5.0.0", forRemoval = true) 73 | @JsonProperty("average_income") 74 | public Integer getAverageIncome() { 75 | return averageIncome(); 76 | } 77 | 78 | /** 79 | * @return The estimated population per square kilometer associated with the 80 | * IP address. This attribute is only available from the Insights web 81 | * service. 82 | * @deprecated Use {@link #populationDensity()} instead. This method will be removed in 6.0.0. 83 | */ 84 | @Deprecated(since = "5.0.0", forRemoval = true) 85 | @JsonProperty("population_density") 86 | public Integer getPopulationDensity() { 87 | return populationDensity(); 88 | } 89 | 90 | /** 91 | * @return The time zone associated with location, as specified by the IANA Time Zone 93 | * Database, e.g., "America/New_York". 94 | * @deprecated Use {@link #timeZone()} instead. This method will be removed in 6.0.0. 95 | */ 96 | @Deprecated(since = "5.0.0", forRemoval = true) 97 | @JsonProperty("time_zone") 98 | public String getTimeZone() { 99 | return timeZone(); 100 | } 101 | 102 | /** 103 | * @return The approximate accuracy radius in kilometers around the 104 | * latitude and longitude for the IP address. This is the radius where we 105 | * have a 67% confidence that the device using the IP address resides 106 | * within the circle centered at the latitude and longitude with the 107 | * provided radius. 108 | * @deprecated Use {@link #accuracyRadius()} instead. This method will be removed in 6.0.0. 109 | */ 110 | @Deprecated(since = "5.0.0", forRemoval = true) 111 | @JsonProperty("accuracy_radius") 112 | public Integer getAccuracyRadius() { 113 | return accuracyRadius(); 114 | } 115 | 116 | /** 117 | * @return The approximate latitude of the location associated with the 118 | * IP address. This value is not precise and should not be used to 119 | * identify a particular address or household. 120 | * @deprecated Use {@link #latitude()} instead. This method will be removed in 6.0.0. 121 | */ 122 | @Deprecated(since = "5.0.0", forRemoval = true) 123 | public Double getLatitude() { 124 | return latitude(); 125 | } 126 | 127 | /** 128 | * @return The approximate longitude of the location associated with the 129 | * IP address. This value is not precise and should not be used to 130 | * identify a particular address or household. 131 | * @deprecated Use {@link #longitude()} instead. This method will be removed in 6.0.0. 132 | */ 133 | @Deprecated(since = "5.0.0", forRemoval = true) 134 | public Double getLongitude() { 135 | return longitude(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/com/maxmind/geoip2/model/CityResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 4 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 5 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 6 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 7 | import static com.maxmind.geoip2.json.File.readJsonFile; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | import static org.junit.jupiter.api.Assertions.assertNull; 11 | 12 | import com.github.tomakehurst.wiremock.junit5.WireMockExtension; 13 | import com.github.tomakehurst.wiremock.junit5.WireMockTest; 14 | import com.maxmind.geoip2.WebServiceClient; 15 | import com.maxmind.geoip2.exception.GeoIp2Exception; 16 | import java.io.IOException; 17 | import java.net.InetAddress; 18 | import java.net.URISyntaxException; 19 | import java.util.Arrays; 20 | import java.util.Collections; 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.extension.RegisterExtension; 24 | 25 | // In addition to testing the CityResponse, this code exercises the locale 26 | // handling of the models 27 | @WireMockTest 28 | public class CityResponseTest { 29 | @RegisterExtension 30 | static WireMockExtension wireMock = WireMockExtension.newInstance() 31 | .options(wireMockConfig().dynamicPort().dynamicHttpsPort()) 32 | .build(); 33 | 34 | @BeforeEach 35 | public void createClient() throws IOException, GeoIp2Exception, 36 | URISyntaxException { 37 | wireMock.stubFor(get(urlEqualTo("/geoip/v2.1/city/1.1.1.2")) 38 | .willReturn(aResponse() 39 | .withStatus(200) 40 | .withHeader("Content-Type", 41 | "application/vnd.maxmind.com-city+json; charset=UTF-8; version=2.1") 42 | .withBody(readJsonFile("city0")))); 43 | } 44 | 45 | 46 | @Test 47 | public void testNames() throws Exception { 48 | WebServiceClient client = new WebServiceClient.Builder(6, "0123456789") 49 | .host("localhost") 50 | .port(wireMock.getPort()) 51 | .disableHttps() 52 | .locales(Arrays.asList("zh-CN", "ru")) 53 | .build(); 54 | 55 | CityResponse city = client.city(InetAddress.getByName("1.1.1.2")); 56 | assertEquals( 57 | "北美洲", 58 | city.continent().name(), 59 | "country.continent().name() does not return 北美洲" 60 | ); 61 | assertEquals( 62 | "美国", 63 | city.country().name(), 64 | "country.country().name() does not return 美国" 65 | ); 66 | assertEquals( 67 | city.country() 68 | .name(), city.country().name(), 69 | "toString() returns getName()" 70 | ); 71 | } 72 | 73 | @Test 74 | public void russianFallback() throws Exception { 75 | WebServiceClient client = new WebServiceClient.Builder(42, 76 | "abcdef123456") 77 | .host("localhost") 78 | .port(wireMock.getPort()) 79 | .disableHttps() 80 | .locales(Arrays.asList("as", "ru")).build(); 81 | 82 | CityResponse city = client.city(InetAddress.getByName("1.1.1.2")); 83 | assertEquals( 84 | "объединяет государства", 85 | city.country().name(), 86 | "country.country().name() does not return объединяет государства" 87 | ); 88 | 89 | } 90 | 91 | @Test 92 | public void testFallback() throws Exception { 93 | WebServiceClient client = new WebServiceClient.Builder(42, 94 | "abcdef123456") 95 | .host("localhost") 96 | .port(wireMock.getPort()) 97 | .disableHttps() 98 | .locales(Arrays.asList("pt", "en", "zh-CN")).build(); 99 | CityResponse city = client.city(InetAddress.getByName("1.1.1.2")); 100 | assertEquals( 101 | "North America", 102 | city.continent().name(), 103 | "en is returned when pt is missing" 104 | ); 105 | 106 | } 107 | 108 | @Test 109 | public void noFallback() throws Exception { 110 | WebServiceClient client = new WebServiceClient.Builder(42, 111 | "abcdef123456") 112 | .host("localhost") 113 | .port(wireMock.getPort()) 114 | .disableHttps() 115 | .locales(Arrays.asList("pt", "es", "af")).build(); 116 | CityResponse city = client.city(InetAddress.getByName("1.1.1.2")); 117 | 118 | assertNull( 119 | city.continent().name(), 120 | "null is returned when locale is not available" 121 | ); 122 | } 123 | 124 | @Test 125 | public void noLocale() throws Exception { 126 | WebServiceClient client = new WebServiceClient.Builder(42, 127 | "abcdef123456") 128 | .host("localhost") 129 | .port(wireMock.getPort()) 130 | .disableHttps() 131 | .build(); 132 | CityResponse city = client.city(InetAddress.getByName("1.1.1.2")); 133 | assertEquals( 134 | "North America", 135 | city.continent().name(), 136 | "en is returned when no locales are specified" 137 | ); 138 | 139 | } 140 | 141 | @Test 142 | public void testMissing() throws Exception { 143 | WebServiceClient client = new WebServiceClient.Builder(42, 144 | "abcdef123456") 145 | .host("localhost") 146 | .port(wireMock.getPort()) 147 | .disableHttps() 148 | .locales(Collections.singletonList("en")).build(); 149 | 150 | CityResponse city = client.city(InetAddress.getByName("1.1.1.2")); 151 | assertNotNull(city.city()); 152 | assertNull(city.city().name(), "null is returned when names object is missing"); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/test/java/com/maxmind/geoip2/model/CountryResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 4 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 5 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 6 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 7 | import static com.maxmind.geoip2.json.File.readJsonFile; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertFalse; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | import com.github.tomakehurst.wiremock.junit5.WireMockExtension; 13 | import com.github.tomakehurst.wiremock.junit5.WireMockTest; 14 | import com.maxmind.geoip2.WebServiceClient; 15 | import com.maxmind.geoip2.exception.GeoIp2Exception; 16 | import java.io.IOException; 17 | import java.net.InetAddress; 18 | import java.net.URISyntaxException; 19 | import org.junit.jupiter.api.BeforeEach; 20 | import org.junit.jupiter.api.Test; 21 | import org.junit.jupiter.api.extension.RegisterExtension; 22 | 23 | @WireMockTest 24 | public class CountryResponseTest { 25 | @RegisterExtension 26 | static WireMockExtension wireMock = WireMockExtension.newInstance() 27 | .options(wireMockConfig().dynamicPort().dynamicHttpsPort()) 28 | .build(); 29 | 30 | private CountryResponse country; 31 | 32 | @BeforeEach 33 | public void createClient() throws IOException, GeoIp2Exception, 34 | URISyntaxException { 35 | wireMock.stubFor(get(urlEqualTo("/geoip/v2.1/country/1.1.1.1")) 36 | .willReturn(aResponse() 37 | .withStatus(200) 38 | .withHeader("Content-Type", 39 | "application/vnd.maxmind.com-country+json; charset=UTF-8; version=2.1") 40 | .withBody(readJsonFile("country0")))); 41 | 42 | WebServiceClient client = new WebServiceClient.Builder(6, "0123456789") 43 | .host("localhost") 44 | .port(wireMock.getPort()) 45 | .disableHttps() 46 | .build(); 47 | 48 | country = client.country(InetAddress.getByName("1.1.1.1")); 49 | } 50 | 51 | @Test 52 | public void testContinent() { 53 | assertEquals( 54 | "NA", 55 | this.country.continent().code(), 56 | "country.continent().code() does not return NA" 57 | ); 58 | assertEquals( 59 | 42, 60 | this.country.continent().geonameId(), 61 | "country.continent().geonameId() does not return 42" 62 | ); 63 | assertEquals( 64 | "North America", 65 | this.country.continent().name(), 66 | "country.continent().name() does not return North America" 67 | ); 68 | } 69 | 70 | @Test 71 | public void testCountry() { 72 | assertFalse( 73 | this.country.country().isInEuropeanUnion(), 74 | "country.country().isInEuropeanUnion() does not return false" 75 | ); 76 | assertEquals( 77 | this.country.country().isoCode(), 78 | "US", 79 | "country.country().code() does not return US" 80 | ); 81 | assertEquals( 82 | 1, 83 | (long) this.country.country().geonameId(), 84 | "country.country().geonameId() does not return 1" 85 | ); 86 | assertEquals( 87 | Integer.valueOf(56), 88 | this.country.country().confidence(), 89 | "country.country().confidence() does not return 56" 90 | ); 91 | assertEquals( 92 | "United States", 93 | this.country.country().name(), 94 | "country.country().name(\"en\") does not return United States" 95 | ); 96 | } 97 | 98 | @Test 99 | public void testRegisteredCountry() { 100 | assertFalse( 101 | this.country.registeredCountry().isInEuropeanUnion(), 102 | "country.registeredCountry().isInEuropeanUnion() does not return false" 103 | ); 104 | assertEquals( 105 | "CA", 106 | this.country.registeredCountry().isoCode(), 107 | "country.registeredCountry().isoCode() does not return CA" 108 | ); 109 | assertEquals( 110 | 2, 111 | (long) this.country.registeredCountry().geonameId(), 112 | "country.registeredCountry().geonameId() does not return 2" 113 | ); 114 | assertEquals( 115 | "Canada", 116 | this.country.registeredCountry().name(), 117 | "country.registeredCountry().name(\"en\") does not return United States" 118 | ); 119 | } 120 | 121 | @Test 122 | public void testRepresentedCountry() { 123 | assertTrue( 124 | this.country.representedCountry().isInEuropeanUnion(), 125 | "country.representedCountry().isInEuropeanUnion() does not return true" 126 | ); 127 | assertEquals( 128 | "GB", 129 | this.country.representedCountry().isoCode(), 130 | "country.representedCountry().code() does not return GB" 131 | ); 132 | assertEquals( 133 | 4, 134 | (long) this.country.representedCountry().geonameId(), 135 | "country.representedCountry().geonameId() does not return 4" 136 | ); 137 | assertEquals( 138 | "United Kingdom", 139 | this.country.representedCountry().name(), 140 | "country.representedCountry().name(\"en\") does not return United Kingdom" 141 | ); 142 | assertEquals( 143 | "military", 144 | this.country.representedCountry().type(), 145 | "country.representedCountry().type() does not return military" 146 | ); 147 | } 148 | 149 | @Test 150 | public void testTraits() { 151 | 152 | assertEquals( 153 | "1.2.3.4", 154 | this.country.traits().ipAddress().getHostAddress(), 155 | "country.traits().getIpAddress does not return 1.2.3.4" 156 | ); 157 | 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/record/RepresentedCountry.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.record; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonInject; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.db.MaxMindDbParameter; 6 | import com.maxmind.geoip2.NamedRecord; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | *

12 | * Contains data for the represented country associated with an IP address. 13 | *

14 | *

15 | * This class contains the country-level data associated with an IP address for 16 | * the IP's represented country. The represented country is the country 17 | * represented by something like a military base. 18 | *

19 | *

20 | * Do not use any of the country names as a database or map key. Use the value 21 | * returned by {@link #geonameId()} or {@link #isoCode()} instead. 22 | *

23 | * 24 | * @param locales The locales to use for retrieving localized names. 25 | * @param confidence A value from 0-100 indicating MaxMind's confidence that the 26 | * country is correct. This attribute is only available from the 27 | * Insights web service and the GeoIP2 Enterprise database. 28 | * @param geonameId The GeoName ID for the country. 29 | * @param isInEuropeanUnion This is true if the country is a member state of the 30 | * European Union. 31 | * @param isoCode The two-character ISO 32 | * 3166-1 alpha code for the country. 33 | * @param names A {@link Map} from locale codes to the name in that locale. 34 | * @param type A string indicating the type of entity that is representing the 35 | * country. Currently, we only return {@code military} but this could 36 | * expand to include other types in the future. 37 | */ 38 | public record RepresentedCountry( 39 | @JacksonInject("locales") 40 | @MaxMindDbParameter(name = "locales") 41 | List locales, 42 | 43 | @JsonProperty("confidence") 44 | @MaxMindDbParameter(name = "confidence") 45 | Integer confidence, 46 | 47 | @JsonProperty("geoname_id") 48 | @MaxMindDbParameter(name = "geoname_id") 49 | Long geonameId, 50 | 51 | @JsonProperty("is_in_european_union") 52 | @MaxMindDbParameter(name = "is_in_european_union", useDefault = true) 53 | boolean isInEuropeanUnion, 54 | 55 | @JsonProperty("iso_code") 56 | @MaxMindDbParameter(name = "iso_code") 57 | String isoCode, 58 | 59 | @JsonProperty("names") 60 | @MaxMindDbParameter(name = "names") 61 | Map names, 62 | 63 | @JsonProperty("type") 64 | @MaxMindDbParameter(name = "type") 65 | String type 66 | ) implements NamedRecord { 67 | 68 | /** 69 | * Compact canonical constructor that ensures immutability and handles null values. 70 | */ 71 | public RepresentedCountry { 72 | locales = locales != null ? List.copyOf(locales) : List.of(); 73 | names = names != null ? Map.copyOf(names) : Map.of(); 74 | } 75 | 76 | /** 77 | * Constructs an instance of {@code RepresentedCountry} with no data. 78 | */ 79 | public RepresentedCountry() { 80 | this(null, null, null, false, null, null, null); 81 | } 82 | 83 | /** 84 | * Constructs an instance of {@code RepresentedCountry}. 85 | * 86 | * @param country The {@code RepresentedCountry} object to copy. 87 | * @param locales The locales to use. 88 | */ 89 | public RepresentedCountry( 90 | RepresentedCountry country, 91 | List locales 92 | ) { 93 | this( 94 | locales, 95 | country.confidence(), 96 | country.geonameId(), 97 | country.isInEuropeanUnion(), 98 | country.isoCode(), 99 | country.names(), 100 | country.type() 101 | ); 102 | } 103 | 104 | /** 105 | * @return A string indicating the type of entity that is representing the 106 | * country. Currently, we only return {@code military} but this could 107 | * expand to include other types in the future. 108 | * @deprecated Use {@link #type()} instead. This method will be removed in 6.0.0. 109 | */ 110 | @Deprecated(since = "5.0.0", forRemoval = true) 111 | public String getType() { 112 | return type(); 113 | } 114 | 115 | /** 116 | * @return A value from 0-100 indicating MaxMind's confidence that the 117 | * country is correct. This attribute is only available from the 118 | * Insights web service and the GeoIP2 Enterprise database. 119 | * @deprecated Use {@link #confidence()} instead. This method will be removed in 6.0.0. 120 | */ 121 | @Deprecated(since = "5.0.0", forRemoval = true) 122 | public Integer getConfidence() { 123 | return confidence(); 124 | } 125 | 126 | /** 127 | * @return The two-character ISO 129 | * 3166-1 alpha code for the country. 130 | * @deprecated Use {@link #isoCode()} instead. This method will be removed in 6.0.0. 131 | */ 132 | @Deprecated(since = "5.0.0", forRemoval = true) 133 | @JsonProperty("iso_code") 134 | public String getIsoCode() { 135 | return isoCode(); 136 | } 137 | 138 | /** 139 | * @return The GeoName ID for the country. 140 | * @deprecated Use {@link #geonameId()} instead. This method will be removed in 6.0.0. 141 | */ 142 | @Deprecated(since = "5.0.0", forRemoval = true) 143 | @JsonProperty("geoname_id") 144 | public Long getGeoNameId() { 145 | return geonameId(); 146 | } 147 | 148 | /** 149 | * @return The name of the country based on the locales list. 150 | * @deprecated Use {@link #name()} instead. This method will be removed in 6.0.0. 151 | */ 152 | @Deprecated(since = "5.0.0", forRemoval = true) 153 | @com.fasterxml.jackson.annotation.JsonIgnore 154 | public String getName() { 155 | return name(); 156 | } 157 | 158 | /** 159 | * @return A {@link Map} from locale codes to the name in that locale. 160 | * @deprecated Use {@link #names()} instead. This method will be removed in 6.0.0. 161 | */ 162 | @Deprecated(since = "5.0.0", forRemoval = true) 163 | @JsonProperty("names") 164 | public Map getNames() { 165 | return names(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/CountryResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.maxmind.db.MaxMindDbParameter; 5 | import com.maxmind.geoip2.JsonSerializable; 6 | import com.maxmind.geoip2.record.Continent; 7 | import com.maxmind.geoip2.record.Country; 8 | import com.maxmind.geoip2.record.MaxMind; 9 | import com.maxmind.geoip2.record.RepresentedCountry; 10 | import com.maxmind.geoip2.record.Traits; 11 | import java.util.List; 12 | 13 | /** 14 | * This class provides a model for the data returned by the Country web service 15 | * and the Country database. 16 | * 17 | * @param continent Continent record for the requested IP address. 18 | * @param country Country record for the requested IP address. This object represents the country 19 | * where MaxMind believes the end user is located. 20 | * @param maxmind MaxMind record containing data related to your account. 21 | * @param registeredCountry Registered country record for the requested IP address. This record 22 | * represents the country where the ISP has registered a given IP block 23 | * and may differ from the user's country. 24 | * @param representedCountry Represented country record for the requested IP address. The 25 | * represented country is used for things like military bases. It is 26 | * only present when the represented country differs from the country. 27 | * @param traits Record for the traits of the requested IP address. 28 | * @see GeoIP2 Web 29 | * Services 30 | */ 31 | public record CountryResponse( 32 | @JsonProperty("continent") 33 | @MaxMindDbParameter(name = "continent") 34 | Continent continent, 35 | 36 | @JsonProperty("country") 37 | @MaxMindDbParameter(name = "country") 38 | Country country, 39 | 40 | @JsonProperty("maxmind") 41 | @MaxMindDbParameter(name = "maxmind") 42 | MaxMind maxmind, 43 | 44 | @JsonProperty("registered_country") 45 | @MaxMindDbParameter(name = "registered_country") 46 | Country registeredCountry, 47 | 48 | @JsonProperty("represented_country") 49 | @MaxMindDbParameter(name = "represented_country") 50 | RepresentedCountry representedCountry, 51 | 52 | @JsonProperty("traits") 53 | @MaxMindDbParameter(name = "traits") 54 | Traits traits 55 | ) implements JsonSerializable { 56 | 57 | /** 58 | * Compact canonical constructor that sets defaults for null values. 59 | */ 60 | public CountryResponse { 61 | continent = continent != null ? continent : new Continent(); 62 | country = country != null ? country : new Country(); 63 | maxmind = maxmind != null ? maxmind : new MaxMind(); 64 | registeredCountry = registeredCountry != null ? registeredCountry : new Country(); 65 | representedCountry = representedCountry != null 66 | ? representedCountry : new RepresentedCountry(); 67 | traits = traits != null ? traits : new Traits(); 68 | } 69 | 70 | /** 71 | * Constructs an instance of {@code CountryResponse} with the specified parameters. 72 | * 73 | * @param response the response 74 | * @param locales the locales 75 | */ 76 | public CountryResponse( 77 | CountryResponse response, 78 | List locales 79 | ) { 80 | this( 81 | new Continent(response.continent(), locales), 82 | new Country(response.country(), locales), 83 | response.maxmind(), 84 | new Country(response.registeredCountry(), locales), 85 | new RepresentedCountry(response.representedCountry(), locales), 86 | response.traits() 87 | ); 88 | } 89 | 90 | /** 91 | * @return MaxMind record containing data related to your account. 92 | * @deprecated Use {@link #maxmind()} instead. This method will be removed in 6.0.0. 93 | */ 94 | @Deprecated(since = "5.0.0", forRemoval = true) 95 | @JsonProperty("maxmind") 96 | public MaxMind getMaxMind() { 97 | return maxmind(); 98 | } 99 | 100 | /** 101 | * @return Registered country record for the requested IP address. This 102 | * record represents the country where the ISP has registered a 103 | * given IP block and may differ from the user's country. 104 | * @deprecated Use {@link #registeredCountry()} instead. This method will be removed in 6.0.0. 105 | */ 106 | @Deprecated(since = "5.0.0", forRemoval = true) 107 | @JsonProperty("registered_country") 108 | public Country getRegisteredCountry() { 109 | return registeredCountry(); 110 | } 111 | 112 | /** 113 | * @return Continent record for the requested IP address. 114 | * @deprecated Use {@link #continent()} instead. This method will be removed in 6.0.0. 115 | */ 116 | @Deprecated(since = "5.0.0", forRemoval = true) 117 | public Continent getContinent() { 118 | return continent(); 119 | } 120 | 121 | /** 122 | * @return Country record for the requested IP address. This object 123 | * represents the country where MaxMind believes the end user is 124 | * located. 125 | * @deprecated Use {@link #country()} instead. This method will be removed in 6.0.0. 126 | */ 127 | @Deprecated(since = "5.0.0", forRemoval = true) 128 | public Country getCountry() { 129 | return country(); 130 | } 131 | 132 | /** 133 | * @return Represented country record for the requested IP address. The 134 | * represented country is used for things like military bases. It is 135 | * only present when the represented country differs from the 136 | * country. 137 | * @deprecated Use {@link #representedCountry()} instead. This method will be removed in 6.0.0. 138 | */ 139 | @Deprecated(since = "5.0.0", forRemoval = true) 140 | @JsonProperty("represented_country") 141 | public RepresentedCountry getRepresentedCountry() { 142 | return representedCountry(); 143 | } 144 | 145 | /** 146 | * @return Record for the traits of the requested IP address. 147 | * @deprecated Use {@link #traits()} instead. This method will be removed in 6.0.0. 148 | */ 149 | @Deprecated(since = "5.0.0", forRemoval = true) 150 | public Traits getTraits() { 151 | return traits(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/IspResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 7 | import com.maxmind.db.MaxMindDbIpAddress; 8 | import com.maxmind.db.MaxMindDbNetwork; 9 | import com.maxmind.db.MaxMindDbParameter; 10 | import com.maxmind.db.Network; 11 | import com.maxmind.geoip2.JsonSerializable; 12 | import com.maxmind.geoip2.NetworkDeserializer; 13 | import java.net.InetAddress; 14 | 15 | /** 16 | * This class provides the GeoIP2 ISP model. 17 | * 18 | * @param autonomousSystemNumber The autonomous system number associated with the IP address. 19 | * @param autonomousSystemOrganization The organization associated with the registered autonomous 20 | * system number for the IP address. 21 | * @param ipAddress The IP address that the data in the model is for. 22 | * @param isp The name of the ISP associated with the IP address. 23 | * @param mobileCountryCode The 24 | * mobile country code (MCC) associated with the IP address and ISP. 25 | * This property is available from the City and Insights web services and 26 | * the GeoIP2 Enterprise database. 27 | * @param mobileNetworkCode The 28 | * mobile network code (MNC) associated with the IP address and ISP. 29 | * This property is available from the City and Insights web services and 30 | * the GeoIP2 Enterprise database. 31 | * @param organization The name of the organization associated with the IP address. 32 | * @param network The network associated with the record. In particular, this is the largest 33 | * network where all the fields besides IP address have the same value. 34 | */ 35 | public record IspResponse( 36 | @JsonProperty("autonomous_system_number") 37 | @MaxMindDbParameter(name = "autonomous_system_number") 38 | Long autonomousSystemNumber, 39 | 40 | @JsonProperty("autonomous_system_organization") 41 | @MaxMindDbParameter(name = "autonomous_system_organization") 42 | String autonomousSystemOrganization, 43 | 44 | @JsonProperty("ip_address") 45 | @MaxMindDbIpAddress 46 | InetAddress ipAddress, 47 | 48 | @JsonProperty("isp") 49 | @MaxMindDbParameter(name = "isp") 50 | String isp, 51 | 52 | @JsonProperty("mobile_country_code") 53 | @MaxMindDbParameter(name = "mobile_country_code") 54 | String mobileCountryCode, 55 | 56 | @JsonProperty("mobile_network_code") 57 | @MaxMindDbParameter(name = "mobile_network_code") 58 | String mobileNetworkCode, 59 | 60 | @JsonProperty("organization") 61 | @MaxMindDbParameter(name = "organization") 62 | String organization, 63 | 64 | @JsonProperty("network") 65 | @JsonDeserialize(using = NetworkDeserializer.class) 66 | @MaxMindDbNetwork 67 | Network network 68 | ) implements JsonSerializable { 69 | 70 | /** 71 | * @return The autonomous system number associated with the IP address. 72 | * @deprecated Use {@link #autonomousSystemNumber()} instead. This method will be removed 73 | * in 6.0.0. 74 | */ 75 | @Deprecated(since = "5.0.0", forRemoval = true) 76 | @JsonProperty("autonomous_system_number") 77 | public Long getAutonomousSystemNumber() { 78 | return autonomousSystemNumber(); 79 | } 80 | 81 | /** 82 | * @return The organization associated with the registered autonomous system 83 | * number for the IP address 84 | * @deprecated Use {@link #autonomousSystemOrganization()} instead. This method will be 85 | * removed in 6.0.0. 86 | */ 87 | @Deprecated(since = "5.0.0", forRemoval = true) 88 | @JsonProperty("autonomous_system_organization") 89 | public String getAutonomousSystemOrganization() { 90 | return autonomousSystemOrganization(); 91 | } 92 | 93 | /** 94 | * @return The IP address that the data in the model is for. 95 | * @deprecated Use {@link #ipAddress()} instead. This method will be removed in 6.0.0. 96 | */ 97 | @Deprecated(since = "5.0.0", forRemoval = true) 98 | @JsonProperty("ip_address") 99 | public String getIpAddress() { 100 | return ipAddress().getHostAddress(); 101 | } 102 | 103 | /** 104 | * @return The name of the ISP associated with the IP address. 105 | * @deprecated Use {@link #isp()} instead. This method will be removed in 6.0.0. 106 | */ 107 | @Deprecated(since = "5.0.0", forRemoval = true) 108 | public String getIsp() { 109 | return isp(); 110 | } 111 | 112 | /** 113 | * @return The 114 | * mobile country code (MCC) associated with the IP address and ISP. 115 | * This property is available from the City and Insights web services and 116 | * the GeoIP2 Enterprise database. 117 | * @deprecated Use {@link #mobileCountryCode()} instead. This method will be removed in 6.0.0. 118 | */ 119 | @Deprecated(since = "5.0.0", forRemoval = true) 120 | @JsonProperty("mobile_country_code") 121 | public String getMobileCountryCode() { 122 | return mobileCountryCode(); 123 | } 124 | 125 | /** 126 | * @return The 127 | * mobile network code (MNC) associated with the IP address and ISP. 128 | * This property is available from the City and Insights web services and 129 | * the GeoIP2 Enterprise database. 130 | * @deprecated Use {@link #mobileNetworkCode()} instead. This method will be removed in 6.0.0. 131 | */ 132 | @Deprecated(since = "5.0.0", forRemoval = true) 133 | @JsonProperty("mobile_network_code") 134 | public String getMobileNetworkCode() { 135 | return mobileNetworkCode(); 136 | } 137 | 138 | /** 139 | * @return The name of the organization associated with the IP address. 140 | * @deprecated Use {@link #organization()} instead. This method will be removed in 6.0.0. 141 | */ 142 | @Deprecated(since = "5.0.0", forRemoval = true) 143 | public String getOrganization() { 144 | return organization(); 145 | } 146 | 147 | /** 148 | * @return The network associated with the record. In particular, this is 149 | * the largest network where all the fields besides IP address have the 150 | * same value. 151 | * @deprecated Use {@link #network()} instead. This method will be removed in 6.0.0. 152 | */ 153 | @Deprecated(since = "5.0.0", forRemoval = true) 154 | @JsonProperty 155 | @JsonSerialize(using = ToStringSerializer.class) 156 | public Network getNetwork() { 157 | return network(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/AnonymousPlusResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 7 | import com.maxmind.db.MaxMindDbConstructor; 8 | import com.maxmind.db.MaxMindDbIpAddress; 9 | import com.maxmind.db.MaxMindDbNetwork; 10 | import com.maxmind.db.MaxMindDbParameter; 11 | import com.maxmind.db.Network; 12 | import com.maxmind.geoip2.JsonSerializable; 13 | import com.maxmind.geoip2.NetworkDeserializer; 14 | import java.net.InetAddress; 15 | import java.time.LocalDate; 16 | 17 | /** 18 | * This class provides the GeoIP Anonymous Plus model. 19 | * 20 | * @param ipAddress The IP address that the data in the model is for. 21 | * @param isAnonymous Whether the IP address belongs to any sort of anonymous network. 22 | * @param isAnonymousVpn Whether the IP address is registered to an anonymous VPN provider. If a 23 | * VPN provider does not register subnets under names associated with them, 24 | * we will likely only flag their IP ranges using isHostingProvider. 25 | * @param isHostingProvider Whether the IP address belongs to a hosting or VPN provider (see 26 | * description of isAnonymousVpn). 27 | * @param isPublicProxy Whether the IP address belongs to a public proxy. 28 | * @param isResidentialProxy Whether the IP address is on a suspected anonymizing network and 29 | * belongs to a residential ISP. 30 | * @param isTorExitNode Whether the IP address is a Tor exit node. 31 | * @param network The network associated with the record. In particular, this is the largest 32 | * network where all the fields besides IP address have the same value. 33 | * @param anonymizerConfidence A score ranging from 1 to 99 that is our percent confidence that 34 | * the network is currently part of an actively used VPN service. 35 | * @param networkLastSeen The last day that the network was sighted in our analysis of anonymized 36 | * networks. 37 | * @param providerName The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated 38 | * with the network. 39 | */ 40 | public record AnonymousPlusResponse( 41 | @JsonProperty("ip_address") 42 | @MaxMindDbIpAddress 43 | InetAddress ipAddress, 44 | 45 | @JsonProperty("is_anonymous") 46 | @MaxMindDbParameter(name = "is_anonymous", useDefault = true) 47 | boolean isAnonymous, 48 | 49 | @JsonProperty("is_anonymous_vpn") 50 | @MaxMindDbParameter(name = "is_anonymous_vpn", useDefault = true) 51 | boolean isAnonymousVpn, 52 | 53 | @JsonProperty("is_hosting_provider") 54 | @MaxMindDbParameter(name = "is_hosting_provider", useDefault = true) 55 | boolean isHostingProvider, 56 | 57 | @JsonProperty("is_public_proxy") 58 | @MaxMindDbParameter(name = "is_public_proxy", useDefault = true) 59 | boolean isPublicProxy, 60 | 61 | @JsonProperty("is_residential_proxy") 62 | @MaxMindDbParameter(name = "is_residential_proxy", useDefault = true) 63 | boolean isResidentialProxy, 64 | 65 | @JsonProperty("is_tor_exit_node") 66 | @MaxMindDbParameter(name = "is_tor_exit_node", useDefault = true) 67 | boolean isTorExitNode, 68 | 69 | @JsonProperty("network") 70 | @JsonDeserialize(using = NetworkDeserializer.class) 71 | @MaxMindDbNetwork 72 | Network network, 73 | 74 | @JsonProperty("anonymizer_confidence") 75 | @MaxMindDbParameter(name = "anonymizer_confidence") 76 | Integer anonymizerConfidence, 77 | 78 | @JsonProperty("network_last_seen") 79 | @MaxMindDbParameter(name = "network_last_seen") 80 | LocalDate networkLastSeen, 81 | 82 | @JsonProperty("provider_name") 83 | @MaxMindDbParameter(name = "provider_name") 84 | String providerName 85 | ) implements JsonSerializable { 86 | 87 | /** 88 | * Constructs an instance of {@code AnonymousPlusResponse} with date parsing 89 | * from MaxMind database. 90 | * 91 | * @param ipAddress the IP address being checked 92 | * @param isAnonymous whether the IP address belongs to any sort of anonymous network 93 | * @param isAnonymousVpn whether the IP address belongs to an anonymous VPN system 94 | * @param isHostingProvider whether the IP address belongs to a hosting provider 95 | * @param isPublicProxy whether the IP address belongs to a public proxy system 96 | * @param isResidentialProxy whether the IP address belongs to a residential proxy system 97 | * @param isTorExitNode whether the IP address is a Tor exit node 98 | * @param network the network associated with the record 99 | * @param anonymizerConfidence confidence that the network is a VPN. 100 | * @param networkLastSeen the last sighting of the network. 101 | * @param providerName the name of the VPN provider. 102 | */ 103 | @MaxMindDbConstructor 104 | public AnonymousPlusResponse( 105 | @MaxMindDbIpAddress InetAddress ipAddress, 106 | @MaxMindDbParameter(name = "is_anonymous", useDefault = true) 107 | boolean isAnonymous, 108 | @MaxMindDbParameter(name = "is_anonymous_vpn", useDefault = true) 109 | boolean isAnonymousVpn, 110 | @MaxMindDbParameter(name = "is_hosting_provider", useDefault = true) 111 | boolean isHostingProvider, 112 | @MaxMindDbParameter(name = "is_public_proxy", useDefault = true) 113 | boolean isPublicProxy, 114 | @MaxMindDbParameter(name = "is_residential_proxy", useDefault = true) 115 | boolean isResidentialProxy, 116 | @MaxMindDbParameter(name = "is_tor_exit_node", useDefault = true) 117 | boolean isTorExitNode, 118 | @MaxMindDbNetwork Network network, 119 | @MaxMindDbParameter(name = "anonymizer_confidence") Integer anonymizerConfidence, 120 | @MaxMindDbParameter(name = "network_last_seen") String networkLastSeen, 121 | @MaxMindDbParameter(name = "provider_name") String providerName 122 | ) { 123 | this( 124 | ipAddress, 125 | isAnonymous, 126 | isAnonymousVpn, 127 | isHostingProvider, 128 | isPublicProxy, 129 | isResidentialProxy, 130 | isTorExitNode, 131 | network, 132 | anonymizerConfidence, 133 | networkLastSeen != null ? LocalDate.parse(networkLastSeen) : null, 134 | providerName 135 | ); 136 | } 137 | 138 | /** 139 | * @return The IP address that the data in the model is for. 140 | * @deprecated Use {@link #ipAddress()} instead. This method will be removed in 6.0.0. 141 | */ 142 | @Deprecated(since = "5.0.0", forRemoval = true) 143 | @JsonProperty("ip_address") 144 | public String getIpAddress() { 145 | return ipAddress().getHostAddress(); 146 | } 147 | 148 | /** 149 | * @return The network associated with the record. In particular, this is 150 | * the largest network where all the fields besides IP address have the 151 | * same value. 152 | * @deprecated Use {@link #network()} instead. This method will be removed in 6.0.0. 153 | */ 154 | @Deprecated(since = "5.0.0", forRemoval = true) 155 | @JsonProperty 156 | @JsonSerialize(using = ToStringSerializer.class) 157 | public Network getNetwork() { 158 | return network(); 159 | } 160 | 161 | /** 162 | * @return A score ranging from 1 to 99 that is our percent confidence that the network is 163 | * currently part of an actively used VPN service. 164 | * @deprecated Use {@link #anonymizerConfidence()} instead. This method will be removed 165 | * in 6.0.0. 166 | */ 167 | @Deprecated(since = "5.0.0", forRemoval = true) 168 | @JsonProperty 169 | public Integer getAnonymizerConfidence() { 170 | return anonymizerConfidence(); 171 | } 172 | 173 | /** 174 | * @return The last day that the network was sighted in our analysis of anonymized networks. 175 | * @deprecated Use {@link #networkLastSeen()} instead. This method will be removed in 6.0.0. 176 | */ 177 | @Deprecated(since = "5.0.0", forRemoval = true) 178 | @JsonProperty 179 | public LocalDate getNetworkLastSeen() { 180 | return networkLastSeen(); 181 | } 182 | 183 | /** 184 | * @return The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated with the 185 | * network. 186 | * @deprecated Use {@link #providerName()} instead. This method will be removed in 6.0.0. 187 | */ 188 | @Deprecated(since = "5.0.0", forRemoval = true) 189 | @JsonProperty 190 | public String getProviderName() { 191 | return providerName(); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/DatabaseProvider.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2; 2 | 3 | import com.maxmind.geoip2.exception.GeoIp2Exception; 4 | import com.maxmind.geoip2.model.AnonymousIpResponse; 5 | import com.maxmind.geoip2.model.AnonymousPlusResponse; 6 | import com.maxmind.geoip2.model.AsnResponse; 7 | import com.maxmind.geoip2.model.CityResponse; 8 | import com.maxmind.geoip2.model.ConnectionTypeResponse; 9 | import com.maxmind.geoip2.model.CountryResponse; 10 | import com.maxmind.geoip2.model.DomainResponse; 11 | import com.maxmind.geoip2.model.EnterpriseResponse; 12 | import com.maxmind.geoip2.model.IpRiskResponse; 13 | import com.maxmind.geoip2.model.IspResponse; 14 | import java.io.IOException; 15 | import java.net.InetAddress; 16 | import java.util.Optional; 17 | 18 | /** 19 | * Interface for GeoIP2 database providers. 20 | */ 21 | public interface DatabaseProvider extends GeoIp2Provider { 22 | 23 | /** 24 | * @param ipAddress IPv4 or IPv6 address to lookup. 25 | * @return A Country model for the requested IP address or empty if it is not in the DB. 26 | * @throws GeoIp2Exception if there is an error looking up the IP 27 | * @throws IOException if there is an IO error 28 | */ 29 | Optional tryCountry(InetAddress ipAddress) throws IOException, 30 | GeoIp2Exception; 31 | 32 | /** 33 | * @param ipAddress IPv4 or IPv6 address to lookup. 34 | * @return A City model for the requested IP address or empty if it is not in the DB. 35 | * @throws GeoIp2Exception if there is an error looking up the IP 36 | * @throws IOException if there is an IO error 37 | */ 38 | Optional tryCity(InetAddress ipAddress) throws IOException, 39 | GeoIp2Exception; 40 | 41 | /** 42 | * Look up an IP address in a GeoIP2 Anonymous IP. 43 | * 44 | * @param ipAddress IPv4 or IPv6 address to lookup. 45 | * @return a AnonymousIpResponse for the requested IP address. 46 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 47 | * @throws java.io.IOException if there is an IO error 48 | */ 49 | AnonymousIpResponse anonymousIp(InetAddress ipAddress) throws IOException, 50 | GeoIp2Exception; 51 | 52 | /** 53 | * Look up an IP address in a GeoIP2 Anonymous IP. 54 | * 55 | * @param ipAddress IPv4 or IPv6 address to lookup. 56 | * @return a AnonymousIpResponse for the requested IP address or empty if it is not in the DB. 57 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 58 | * @throws java.io.IOException if there is an IO error 59 | */ 60 | Optional tryAnonymousIp(InetAddress ipAddress) throws IOException, 61 | GeoIp2Exception; 62 | 63 | /** 64 | * Look up an IP address in a GeoIP2 Anonymous Plus. 65 | * 66 | * @param ipAddress IPv4 or IPv6 address to lookup. 67 | * @return a AnonymousPlusResponse for the requested IP address. 68 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 69 | * @throws java.io.IOException if there is an IO error 70 | */ 71 | AnonymousPlusResponse anonymousPlus(InetAddress ipAddress) throws IOException, 72 | GeoIp2Exception; 73 | 74 | /** 75 | * Look up an IP address in a GeoIP2 Anonymous Plus. 76 | * 77 | * @param ipAddress IPv4 or IPv6 address to lookup. 78 | * @return a AnonymousPlusResponse for the requested IP address or empty if it is not in the DB. 79 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 80 | * @throws java.io.IOException if there is an IO error 81 | */ 82 | Optional tryAnonymousPlus(InetAddress ipAddress) throws IOException, 83 | GeoIp2Exception; 84 | 85 | /** 86 | * Look up an IP address in a GeoIP2 IP Risk database. 87 | * 88 | * @param ipAddress IPv4 or IPv6 address to lookup. 89 | * @return an IpRiskResponse for the requested IP address. 90 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 91 | * @throws java.io.IOException if there is an IO error 92 | */ 93 | IpRiskResponse ipRisk(InetAddress ipAddress) throws IOException, 94 | GeoIp2Exception; 95 | 96 | /** 97 | * Look up an IP address in a GeoIP2 IP Risk database. 98 | * 99 | * @param ipAddress IPv4 or IPv6 address to lookup. 100 | * @return an IPRiskResponse for the requested IP address or empty if it is not in the DB. 101 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 102 | * @throws java.io.IOException if there is an IO error 103 | */ 104 | Optional tryIpRisk(InetAddress ipAddress) throws IOException, 105 | GeoIp2Exception; 106 | 107 | /** 108 | * Look up an IP address in a GeoLite2 ASN database. 109 | * 110 | * @param ipAddress IPv4 or IPv6 address to lookup. 111 | * @return an IspResponse for the requested IP address. 112 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 113 | * @throws java.io.IOException if there is an IO error 114 | */ 115 | AsnResponse asn(InetAddress ipAddress) throws IOException, 116 | GeoIp2Exception; 117 | 118 | /** 119 | * Look up an IP address in a GeoLite2 ASN database. 120 | * 121 | * @param ipAddress IPv4 or IPv6 address to lookup. 122 | * @return an IspResponse for the requested IP address or empty if it is not in the DB. 123 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 124 | * @throws java.io.IOException if there is an IO error 125 | */ 126 | Optional tryAsn(InetAddress ipAddress) throws IOException, 127 | GeoIp2Exception; 128 | 129 | /** 130 | * Look up an IP address in a GeoIP2 Connection Type database. 131 | * 132 | * @param ipAddress IPv4 or IPv6 address to lookup. 133 | * @return a ConnectTypeResponse for the requested IP address. 134 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 135 | * @throws java.io.IOException if there is an IO error 136 | */ 137 | ConnectionTypeResponse connectionType(InetAddress ipAddress) 138 | throws IOException, GeoIp2Exception; 139 | 140 | /** 141 | * Look up an IP address in a GeoIP2 Connection Type database. 142 | * 143 | * @param ipAddress IPv4 or IPv6 address to lookup. 144 | * @return a ConnectTypeResponse for the requested IP address or empty if it is not in the DB. 145 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 146 | * @throws java.io.IOException if there is an IO error 147 | */ 148 | Optional tryConnectionType(InetAddress ipAddress) 149 | throws IOException, GeoIp2Exception; 150 | 151 | /** 152 | * Look up an IP address in a GeoIP2 Domain database. 153 | * 154 | * @param ipAddress IPv4 or IPv6 address to lookup. 155 | * @return a DomainResponse for the requested IP address. 156 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 157 | * @throws java.io.IOException if there is an IO error 158 | */ 159 | DomainResponse domain(InetAddress ipAddress) throws IOException, 160 | GeoIp2Exception; 161 | 162 | /** 163 | * Look up an IP address in a GeoIP2 Domain database. 164 | * 165 | * @param ipAddress IPv4 or IPv6 address to lookup. 166 | * @return a DomainResponse for the requested IP address or empty if it is not in the DB. 167 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 168 | * @throws java.io.IOException if there is an IO error 169 | */ 170 | Optional tryDomain(InetAddress ipAddress) throws IOException, 171 | GeoIp2Exception; 172 | 173 | /** 174 | * Look up an IP address in a GeoIP2 Enterprise database. 175 | * 176 | * @param ipAddress IPv4 or IPv6 address to lookup. 177 | * @return an EnterpriseResponse for the requested IP address. 178 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 179 | * @throws java.io.IOException if there is an IO error 180 | */ 181 | EnterpriseResponse enterprise(InetAddress ipAddress) throws IOException, 182 | GeoIp2Exception; 183 | 184 | /** 185 | * Look up an IP address in a GeoIP2 Enterprise database. 186 | * 187 | * @param ipAddress IPv4 or IPv6 address to lookup. 188 | * @return an EnterpriseResponse for the requested IP address or empty if it is not in the DB. 189 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 190 | * @throws java.io.IOException if there is an IO error 191 | */ 192 | Optional tryEnterprise(InetAddress ipAddress) throws IOException, 193 | GeoIp2Exception; 194 | 195 | /** 196 | * Look up an IP address in a GeoIP2 ISP database. 197 | * 198 | * @param ipAddress IPv4 or IPv6 address to lookup. 199 | * @return an IspResponse for the requested IP address. 200 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 201 | * @throws java.io.IOException if there is an IO error 202 | */ 203 | IspResponse isp(InetAddress ipAddress) throws IOException, 204 | GeoIp2Exception; 205 | 206 | /** 207 | * Look up an IP address in a GeoIP2 ISP database. 208 | * 209 | * @param ipAddress IPv4 or IPv6 address to look up or empty if it is not in the DB. 210 | * @return an IspResponse for the requested IP address. 211 | * @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP 212 | * @throws java.io.IOException if there is an IO error 213 | */ 214 | Optional tryIsp(InetAddress ipAddress) throws IOException, 215 | GeoIp2Exception; 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/InsightsResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.geoip2.JsonSerializable; 6 | import com.maxmind.geoip2.record.Anonymizer; 7 | import com.maxmind.geoip2.record.City; 8 | import com.maxmind.geoip2.record.Continent; 9 | import com.maxmind.geoip2.record.Country; 10 | import com.maxmind.geoip2.record.Location; 11 | import com.maxmind.geoip2.record.MaxMind; 12 | import com.maxmind.geoip2.record.Postal; 13 | import com.maxmind.geoip2.record.RepresentedCountry; 14 | import com.maxmind.geoip2.record.Subdivision; 15 | import com.maxmind.geoip2.record.Traits; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * This class provides a model for the data returned by the Insights web 21 | * service. 22 | * 23 | * @param anonymizer Anonymizer record for the requested IP address. This contains information 24 | * about whether the IP address belongs to an anonymizing network such as a VPN, 25 | * proxy, or Tor exit node. 26 | * @param city City record for the requested IP address. 27 | * @param continent Continent record for the requested IP address. 28 | * @param country Country record for the requested IP address. This object represents the country 29 | * where MaxMind believes the end user is located. 30 | * @param location Location record for the requested IP address. 31 | * @param maxmind MaxMind record containing data related to your account. 32 | * @param postal Postal record for the requested IP address. 33 | * @param registeredCountry Registered country record for the requested IP address. This record 34 | * represents the country where the ISP has registered a given IP block 35 | * and may differ from the user's country. 36 | * @param representedCountry Represented country record for the requested IP address. The 37 | * represented country is used for things like military bases. It is 38 | * only present when the represented country differs from the country. 39 | * @param subdivisions An {@link List} of {@link Subdivision} objects representing the country 40 | * subdivisions for the requested IP address. The number and type of 41 | * subdivisions varies by country, but a subdivision is typically a state, 42 | * province, county, etc. Subdivisions are ordered from most general (largest) 43 | * to most specific (smallest). If the response did not contain any 44 | * subdivisions, this is an empty list. 45 | * @param traits Record for the traits of the requested IP address. 46 | * @see GeoIP2 Web 47 | * Services 48 | */ 49 | public record InsightsResponse( 50 | @JsonProperty("anonymizer") 51 | Anonymizer anonymizer, 52 | 53 | @JsonProperty("city") 54 | City city, 55 | 56 | @JsonProperty("continent") 57 | Continent continent, 58 | 59 | @JsonProperty("country") 60 | Country country, 61 | 62 | @JsonProperty("location") 63 | Location location, 64 | 65 | @JsonProperty("maxmind") 66 | MaxMind maxmind, 67 | 68 | @JsonProperty("postal") 69 | Postal postal, 70 | 71 | @JsonProperty("registered_country") 72 | Country registeredCountry, 73 | 74 | @JsonProperty("represented_country") 75 | RepresentedCountry representedCountry, 76 | 77 | @JsonProperty("subdivisions") 78 | List subdivisions, 79 | 80 | @JsonProperty("traits") 81 | Traits traits 82 | ) implements JsonSerializable { 83 | 84 | /** 85 | * Compact canonical constructor that sets defaults for null values. 86 | */ 87 | public InsightsResponse { 88 | anonymizer = anonymizer != null ? anonymizer : new Anonymizer(); 89 | city = city != null ? city : new City(); 90 | continent = continent != null ? continent : new Continent(); 91 | country = country != null ? country : new Country(); 92 | location = location != null ? location : new Location(); 93 | maxmind = maxmind != null ? maxmind : new MaxMind(); 94 | postal = postal != null ? postal : new Postal(); 95 | registeredCountry = registeredCountry != null ? registeredCountry : new Country(); 96 | representedCountry = representedCountry != null 97 | ? representedCountry : new RepresentedCountry(); 98 | subdivisions = subdivisions != null ? List.copyOf(subdivisions) : List.of(); 99 | traits = traits != null ? traits : new Traits(); 100 | } 101 | 102 | /** 103 | * @return City record for the requested IP address. 104 | * @deprecated Use {@link #city()} instead. This method will be removed in 6.0.0. 105 | */ 106 | @Deprecated(since = "5.0.0", forRemoval = true) 107 | public City getCity() { 108 | return city(); 109 | } 110 | 111 | /** 112 | * @return Continent record for the requested IP address. 113 | * @deprecated Use {@link #continent()} instead. This method will be removed in 6.0.0. 114 | */ 115 | @Deprecated(since = "5.0.0", forRemoval = true) 116 | public Continent getContinent() { 117 | return continent(); 118 | } 119 | 120 | /** 121 | * @return Country record for the requested IP address. This object 122 | * represents the country where MaxMind believes the end user is 123 | * located. 124 | * @deprecated Use {@link #country()} instead. This method will be removed in 6.0.0. 125 | */ 126 | @Deprecated(since = "5.0.0", forRemoval = true) 127 | public Country getCountry() { 128 | return country(); 129 | } 130 | 131 | /** 132 | * @return Location record for the requested IP address. 133 | * @deprecated Use {@link #location()} instead. This method will be removed in 6.0.0. 134 | */ 135 | @Deprecated(since = "5.0.0", forRemoval = true) 136 | public Location getLocation() { 137 | return location(); 138 | } 139 | 140 | /** 141 | * @return MaxMind record containing data related to your account. 142 | * @deprecated Use {@link #maxmind()} instead. This method will be removed in 6.0.0. 143 | */ 144 | @Deprecated(since = "5.0.0", forRemoval = true) 145 | @JsonProperty("maxmind") 146 | public MaxMind getMaxMind() { 147 | return maxmind(); 148 | } 149 | 150 | /** 151 | * @return the postal 152 | * @deprecated Use {@link #postal()} instead. This method will be removed in 6.0.0. 153 | */ 154 | @Deprecated(since = "5.0.0", forRemoval = true) 155 | public Postal getPostal() { 156 | return postal(); 157 | } 158 | 159 | /** 160 | * @return Registered country record for the requested IP address. This 161 | * record represents the country where the ISP has registered a 162 | * given IP block and may differ from the user's country. 163 | * @deprecated Use {@link #registeredCountry()} instead. This method will be removed in 6.0.0. 164 | */ 165 | @Deprecated(since = "5.0.0", forRemoval = true) 166 | @JsonProperty("registered_country") 167 | public Country getRegisteredCountry() { 168 | return registeredCountry(); 169 | } 170 | 171 | /** 172 | * @return Represented country record for the requested IP address. The 173 | * represented country is used for things like military bases. It is 174 | * only present when the represented country differs from the 175 | * country. 176 | * @deprecated Use {@link #representedCountry()} instead. This method will be removed in 6.0.0. 177 | */ 178 | @Deprecated(since = "5.0.0", forRemoval = true) 179 | @JsonProperty("represented_country") 180 | public RepresentedCountry getRepresentedCountry() { 181 | return representedCountry(); 182 | } 183 | 184 | /** 185 | * @return An {@link List} of {@link Subdivision} objects representing the 186 | * country subdivisions for the requested IP address. The number and 187 | * type of subdivisions varies by country, but a subdivision is 188 | * typically a state, province, county, etc. Subdivisions are 189 | * ordered from most general (largest) to most specific (smallest). 190 | * If the response did not contain any subdivisions, this method 191 | * returns an empty array. 192 | * @deprecated Use {@link #subdivisions()} instead. This method will be removed in 6.0.0. 193 | */ 194 | @Deprecated(since = "5.0.0", forRemoval = true) 195 | public List getSubdivisions() { 196 | return new ArrayList<>(subdivisions()); 197 | } 198 | 199 | /** 200 | * @return Record for the traits of the requested IP address. 201 | * @deprecated Use {@link #traits()} instead. This method will be removed in 6.0.0. 202 | */ 203 | @Deprecated(since = "5.0.0", forRemoval = true) 204 | public Traits getTraits() { 205 | return traits(); 206 | } 207 | 208 | /** 209 | * @return An object representing the most specific subdivision returned. If 210 | * the response did not contain any subdivisions, this method 211 | * returns an empty {@link Subdivision} object. 212 | */ 213 | @JsonIgnore 214 | public Subdivision mostSpecificSubdivision() { 215 | if (subdivisions().isEmpty()) { 216 | return new Subdivision(); 217 | } 218 | return subdivisions().get(subdivisions().size() - 1); 219 | } 220 | 221 | /** 222 | * @return An object representing the most specific subdivision returned. If 223 | * the response did not contain any subdivisions, this method 224 | * returns an empty {@link Subdivision} object. 225 | * @deprecated Use {@link #mostSpecificSubdivision()} instead. This method will be removed 226 | * in 6.0.0. 227 | */ 228 | @JsonIgnore 229 | @Deprecated(since = "5.0.0", forRemoval = true) 230 | public Subdivision getMostSpecificSubdivision() { 231 | return mostSpecificSubdivision(); 232 | } 233 | 234 | /** 235 | * @return An object representing the least specific subdivision returned. If 236 | * the response did not contain any subdivisions, this method 237 | * returns an empty {@link Subdivision} object. 238 | */ 239 | @JsonIgnore 240 | public Subdivision leastSpecificSubdivision() { 241 | if (subdivisions().isEmpty()) { 242 | return new Subdivision(); 243 | } 244 | return subdivisions().get(0); 245 | } 246 | 247 | /** 248 | * @return An object representing the least specific subdivision returned. If 249 | * the response did not contain any subdivisions, this method 250 | * returns an empty {@link Subdivision} object. 251 | * @deprecated Use {@link #leastSpecificSubdivision()} instead. This method will be removed 252 | * in 6.0.0. 253 | */ 254 | @JsonIgnore 255 | @Deprecated(since = "5.0.0", forRemoval = true) 256 | public Subdivision getLeastSpecificSubdivision() { 257 | return leastSpecificSubdivision(); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/EnterpriseResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.db.MaxMindDbParameter; 6 | import com.maxmind.geoip2.JsonSerializable; 7 | import com.maxmind.geoip2.record.City; 8 | import com.maxmind.geoip2.record.Continent; 9 | import com.maxmind.geoip2.record.Country; 10 | import com.maxmind.geoip2.record.Location; 11 | import com.maxmind.geoip2.record.MaxMind; 12 | import com.maxmind.geoip2.record.Postal; 13 | import com.maxmind.geoip2.record.RepresentedCountry; 14 | import com.maxmind.geoip2.record.Subdivision; 15 | import com.maxmind.geoip2.record.Traits; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | *

21 | * This class provides a model for the data returned by the GeoIP2 Enterprise 22 | * database 23 | *

24 | * 25 | * @param city City record for the requested IP address. 26 | * @param continent Continent record for the requested IP address. 27 | * @param country Country record for the requested IP address. This object represents the country 28 | * where MaxMind believes the end user is located. 29 | * @param location Location record for the requested IP address. 30 | * @param maxmind MaxMind record containing data related to your account. 31 | * @param postal Postal record for the requested IP address. 32 | * @param registeredCountry Registered country record for the requested IP address. This record 33 | * represents the country where the ISP has registered a given IP block 34 | * and may differ from the user's country. 35 | * @param representedCountry Represented country record for the requested IP address. The 36 | * represented country is used for things like military bases. It is 37 | * only present when the represented country differs from the country. 38 | * @param subdivisions An {@link List} of {@link Subdivision} objects representing the country 39 | * subdivisions for the requested IP address. The number and type of 40 | * subdivisions varies by country, but a subdivision is typically a state, 41 | * province, county, etc. Subdivisions are ordered from most general (largest) 42 | * to most specific (smallest). If the response did not contain any 43 | * subdivisions, this is an empty list. 44 | * @param traits Record for the traits of the requested IP address. 45 | */ 46 | public record EnterpriseResponse( 47 | @JsonProperty("city") 48 | @MaxMindDbParameter(name = "city") 49 | City city, 50 | 51 | @JsonProperty("continent") 52 | @MaxMindDbParameter(name = "continent") 53 | Continent continent, 54 | 55 | @JsonProperty("country") 56 | @MaxMindDbParameter(name = "country") 57 | Country country, 58 | 59 | @JsonProperty("location") 60 | @MaxMindDbParameter(name = "location") 61 | Location location, 62 | 63 | @JsonProperty("maxmind") 64 | @MaxMindDbParameter(name = "maxmind") 65 | MaxMind maxmind, 66 | 67 | @JsonProperty("postal") 68 | @MaxMindDbParameter(name = "postal") 69 | Postal postal, 70 | 71 | @JsonProperty("registered_country") 72 | @MaxMindDbParameter(name = "registered_country") 73 | Country registeredCountry, 74 | 75 | @JsonProperty("represented_country") 76 | @MaxMindDbParameter(name = "represented_country") 77 | RepresentedCountry representedCountry, 78 | 79 | @JsonProperty("subdivisions") 80 | @MaxMindDbParameter(name = "subdivisions") 81 | List subdivisions, 82 | 83 | @JsonProperty("traits") 84 | @MaxMindDbParameter(name = "traits") 85 | Traits traits 86 | ) implements JsonSerializable { 87 | 88 | /** 89 | * Compact canonical constructor that sets defaults for null values. 90 | */ 91 | public EnterpriseResponse { 92 | city = city != null ? city : new City(); 93 | continent = continent != null ? continent : new Continent(); 94 | country = country != null ? country : new Country(); 95 | location = location != null ? location : new Location(); 96 | maxmind = maxmind != null ? maxmind : new MaxMind(); 97 | postal = postal != null ? postal : new Postal(); 98 | registeredCountry = registeredCountry != null ? registeredCountry : new Country(); 99 | representedCountry = representedCountry != null 100 | ? representedCountry : new RepresentedCountry(); 101 | subdivisions = subdivisions != null ? List.copyOf(subdivisions) : List.of(); 102 | traits = traits != null ? traits : new Traits(); 103 | } 104 | 105 | /** 106 | * Constructs an instance of {@code EnterpriseResponse} with only required parameters. 107 | * 108 | * @param response the response 109 | * @param locales the locales 110 | */ 111 | public EnterpriseResponse( 112 | EnterpriseResponse response, 113 | List locales 114 | ) { 115 | this( 116 | new City(response.city(), locales), 117 | new Continent(response.continent(), locales), 118 | new Country(response.country(), locales), 119 | response.location(), 120 | response.maxmind(), 121 | response.postal(), 122 | new Country(response.registeredCountry(), locales), 123 | new RepresentedCountry(response.representedCountry(), locales), 124 | mapSubdivisions(response.subdivisions(), locales), 125 | response.traits() 126 | ); 127 | } 128 | 129 | private static ArrayList mapSubdivisions( 130 | List subdivisions, 131 | List locales 132 | ) { 133 | var subdivisions2 = new ArrayList(subdivisions.size()); 134 | for (var subdivision : subdivisions) { 135 | subdivisions2.add(new Subdivision(subdivision, locales)); 136 | } 137 | return subdivisions2; 138 | } 139 | 140 | /** 141 | * @return City record for the requested IP address. 142 | * @deprecated Use {@link #city()} instead. This method will be removed in 6.0.0. 143 | */ 144 | @Deprecated(since = "5.0.0", forRemoval = true) 145 | public City getCity() { 146 | return city(); 147 | } 148 | 149 | /** 150 | * @return Continent record for the requested IP address. 151 | * @deprecated Use {@link #continent()} instead. This method will be removed in 6.0.0. 152 | */ 153 | @Deprecated(since = "5.0.0", forRemoval = true) 154 | public Continent getContinent() { 155 | return continent(); 156 | } 157 | 158 | /** 159 | * @return Country record for the requested IP address. This object 160 | * represents the country where MaxMind believes the end user is 161 | * located. 162 | * @deprecated Use {@link #country()} instead. This method will be removed in 6.0.0. 163 | */ 164 | @Deprecated(since = "5.0.0", forRemoval = true) 165 | public Country getCountry() { 166 | return country(); 167 | } 168 | 169 | /** 170 | * @return Location record for the requested IP address. 171 | * @deprecated Use {@link #location()} instead. This method will be removed in 6.0.0. 172 | */ 173 | @Deprecated(since = "5.0.0", forRemoval = true) 174 | public Location getLocation() { 175 | return location(); 176 | } 177 | 178 | /** 179 | * @return MaxMind record containing data related to your account. 180 | * @deprecated Use {@link #maxmind()} instead. This method will be removed in 6.0.0. 181 | */ 182 | @Deprecated(since = "5.0.0", forRemoval = true) 183 | @JsonProperty("maxmind") 184 | public MaxMind getMaxMind() { 185 | return maxmind(); 186 | } 187 | 188 | /** 189 | * @return the postal 190 | * @deprecated Use {@link #postal()} instead. This method will be removed in 6.0.0. 191 | */ 192 | @Deprecated(since = "5.0.0", forRemoval = true) 193 | public Postal getPostal() { 194 | return postal(); 195 | } 196 | 197 | /** 198 | * @return Registered country record for the requested IP address. This 199 | * record represents the country where the ISP has registered a 200 | * given IP block and may differ from the user's country. 201 | * @deprecated Use {@link #registeredCountry()} instead. This method will be removed in 6.0.0. 202 | */ 203 | @Deprecated(since = "5.0.0", forRemoval = true) 204 | @JsonProperty("registered_country") 205 | public Country getRegisteredCountry() { 206 | return registeredCountry(); 207 | } 208 | 209 | /** 210 | * @return Represented country record for the requested IP address. The 211 | * represented country is used for things like military bases. It is 212 | * only present when the represented country differs from the 213 | * country. 214 | * @deprecated Use {@link #representedCountry()} instead. This method will be removed in 6.0.0. 215 | */ 216 | @Deprecated(since = "5.0.0", forRemoval = true) 217 | @JsonProperty("represented_country") 218 | public RepresentedCountry getRepresentedCountry() { 219 | return representedCountry(); 220 | } 221 | 222 | /** 223 | * @return An {@link List} of {@link Subdivision} objects representing the 224 | * country subdivisions for the requested IP address. The number and 225 | * type of subdivisions varies by country, but a subdivision is 226 | * typically a state, province, county, etc. Subdivisions are 227 | * ordered from most general (largest) to most specific (smallest). 228 | * If the response did not contain any subdivisions, this method 229 | * returns an empty array. 230 | * @deprecated Use {@link #subdivisions()} instead. This method will be removed in 6.0.0. 231 | */ 232 | @Deprecated(since = "5.0.0", forRemoval = true) 233 | public List getSubdivisions() { 234 | return new ArrayList<>(subdivisions()); 235 | } 236 | 237 | /** 238 | * @return Record for the traits of the requested IP address. 239 | * @deprecated Use {@link #traits()} instead. This method will be removed in 6.0.0. 240 | */ 241 | @Deprecated(since = "5.0.0", forRemoval = true) 242 | public Traits getTraits() { 243 | return traits(); 244 | } 245 | 246 | /** 247 | * @return An object representing the most specific subdivision returned. If 248 | * the response did not contain any subdivisions, this method 249 | * returns an empty {@link Subdivision} object. 250 | */ 251 | @JsonIgnore 252 | public Subdivision mostSpecificSubdivision() { 253 | if (subdivisions().isEmpty()) { 254 | return new Subdivision(); 255 | } 256 | return subdivisions().get(subdivisions().size() - 1); 257 | } 258 | 259 | /** 260 | * @return An object representing the most specific subdivision returned. If 261 | * the response did not contain any subdivisions, this method 262 | * returns an empty {@link Subdivision} object. 263 | * @deprecated Use {@link #mostSpecificSubdivision()} instead. This method will be removed 264 | * in 6.0.0. 265 | */ 266 | @JsonIgnore 267 | @Deprecated(since = "5.0.0", forRemoval = true) 268 | public Subdivision getMostSpecificSubdivision() { 269 | return mostSpecificSubdivision(); 270 | } 271 | 272 | /** 273 | * @return An object representing the least specific subdivision returned. If 274 | * the response did not contain any subdivisions, this method 275 | * returns an empty {@link Subdivision} object. 276 | */ 277 | @JsonIgnore 278 | public Subdivision leastSpecificSubdivision() { 279 | if (subdivisions().isEmpty()) { 280 | return new Subdivision(); 281 | } 282 | return subdivisions().get(0); 283 | } 284 | 285 | /** 286 | * @return An object representing the least specific subdivision returned. If 287 | * the response did not contain any subdivisions, this method 288 | * returns an empty {@link Subdivision} object. 289 | * @deprecated Use {@link #leastSpecificSubdivision()} instead. This method will be removed 290 | * in 6.0.0. 291 | */ 292 | @JsonIgnore 293 | @Deprecated(since = "5.0.0", forRemoval = true) 294 | public Subdivision getLeastSpecificSubdivision() { 295 | return leastSpecificSubdivision(); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 4 | import static com.github.tomakehurst.wiremock.client.WireMock.get; 5 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 6 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 7 | import static com.maxmind.geoip2.json.File.readJsonFile; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | import static org.junit.jupiter.api.Assertions.fail; 12 | 13 | import com.github.tomakehurst.wiremock.junit5.WireMockExtension; 14 | import com.github.tomakehurst.wiremock.junit5.WireMockTest; 15 | import com.maxmind.geoip2.WebServiceClient; 16 | import com.maxmind.geoip2.exception.GeoIp2Exception; 17 | import com.maxmind.geoip2.model.ConnectionTypeResponse.ConnectionType; 18 | import com.maxmind.geoip2.record.Anonymizer; 19 | import com.maxmind.geoip2.record.Location; 20 | import com.maxmind.geoip2.record.MaxMind; 21 | import com.maxmind.geoip2.record.Postal; 22 | import com.maxmind.geoip2.record.Subdivision; 23 | import com.maxmind.geoip2.record.Traits; 24 | import java.io.IOException; 25 | import java.net.InetAddress; 26 | import java.net.URISyntaxException; 27 | import java.time.LocalDate; 28 | import java.util.List; 29 | import org.junit.jupiter.api.BeforeEach; 30 | import org.junit.jupiter.api.Test; 31 | import org.junit.jupiter.api.extension.RegisterExtension; 32 | 33 | @WireMockTest 34 | public class InsightsResponseTest { 35 | @RegisterExtension 36 | static WireMockExtension wireMock = WireMockExtension.newInstance() 37 | .options(wireMockConfig().dynamicPort().dynamicHttpsPort()) 38 | .build(); 39 | 40 | private InsightsResponse insights; 41 | 42 | @BeforeEach 43 | public void createClient() throws IOException, GeoIp2Exception, 44 | URISyntaxException { 45 | wireMock.stubFor(get(urlEqualTo("/geoip/v2.1/insights/1.1.1.1")) 46 | .willReturn(aResponse() 47 | .withStatus(200) 48 | .withHeader("Content-Type", 49 | "application/vnd.maxmind.com-insights+json; charset=UTF-8; version=2.1") 50 | .withBody(readJsonFile("insights0")))); 51 | wireMock.stubFor(get(urlEqualTo("/geoip/v2.1/insights/1.1.1.2")) 52 | .willReturn(aResponse() 53 | .withStatus(200) 54 | .withHeader("Content-Type", 55 | "application/vnd.maxmind.com-insights+json; charset=UTF-8; version=2.1") 56 | .withBody(readJsonFile("insights1")))); 57 | 58 | WebServiceClient client = new WebServiceClient.Builder(6, "0123456789") 59 | .host("localhost") 60 | .port(wireMock.getPort()) 61 | .disableHttps() 62 | .build(); 63 | 64 | this.insights = client.insights(InetAddress.getByName("1.1.1.1")); 65 | } 66 | 67 | @Test 68 | public void testSubdivisionsList() { 69 | List subdivisionsList = this.insights.subdivisions(); 70 | assertNotNull(subdivisionsList, "city.getSubdivisionsList returns null"); 71 | if (subdivisionsList.isEmpty()) { 72 | fail("subdivisionsList is empty"); 73 | } 74 | Subdivision subdivision = subdivisionsList.get(0); 75 | assertEquals( 76 | Integer.valueOf(88), 77 | subdivision.confidence(), 78 | "subdivision.confidence() does not return 88" 79 | ); 80 | assertEquals( 81 | 574635, 82 | subdivision.geonameId().intValue(), 83 | "subdivision.geonameId() does not return 574635" 84 | ); 85 | assertEquals( 86 | "MN", 87 | subdivision.isoCode(), 88 | "subdivision.code() does not return MN" 89 | ); 90 | } 91 | 92 | @Test 93 | public void mostSpecificSubdivision() { 94 | assertEquals( 95 | "TT", 96 | this.insights.mostSpecificSubdivision().isoCode(), 97 | "Most specific subdivision returns last subdivision" 98 | ); 99 | } 100 | 101 | @Test 102 | public void leastSpecificSubdivision() { 103 | assertEquals( 104 | "MN", 105 | this.insights.leastSpecificSubdivision().isoCode(), 106 | "Most specific subdivision returns first subdivision" 107 | ); 108 | } 109 | 110 | @Test 111 | public void testTraits() { 112 | Traits traits = this.insights.traits(); 113 | 114 | assertNotNull(traits, "city.traits() returns null"); 115 | assertEquals( 116 | Long.valueOf(1234), 117 | traits.autonomousSystemNumber(), 118 | "traits.autonomousSystemNumber() does not return 1234" 119 | ); 120 | assertEquals( 121 | 122 | "AS Organization", 123 | traits.autonomousSystemOrganization(), 124 | "traits.autonomousSystemOrganization() does not return AS Organization" 125 | ); 126 | assertEquals( 127 | 128 | ConnectionType.CABLE_DSL, 129 | traits.connectionType(), 130 | "traits.connectionType() does not return Cable/DSL" 131 | ); 132 | assertEquals( 133 | "example.com", 134 | traits.domain(), 135 | "traits.domain() does not return example.com" 136 | ); 137 | assertEquals( 138 | "1.2.3.4", 139 | traits.ipAddress().getHostAddress(), 140 | "traits.ipAddress() does not return 1.2.3.4" 141 | ); 142 | assertTrue(traits.isAnonymous(), "traits.isAnonymous() returns true"); 143 | assertTrue(traits.isAnonymousVpn(), "traits.isAnonymousVpn() returns true"); 144 | assertTrue(traits.isHostingProvider(), "traits.isHostingProvider() returns true"); 145 | assertTrue(traits.isPublicProxy(), "traits.isPublicProxy() returns true"); 146 | assertTrue(traits.isResidentialProxy(), "traits.isResidentialProxy() returns true"); 147 | assertTrue(traits.isTorExitNode(), "traits.isTorExitNode() returns true"); 148 | assertEquals( 149 | "Comcast", 150 | traits.isp(), 151 | "traits.isp() does not return Comcast" 152 | ); 153 | assertEquals( 154 | "Blorg", 155 | traits.organization(), 156 | "traits.organization() does not return Blorg" 157 | ); 158 | assertEquals( 159 | "college", 160 | traits.userType(), 161 | "traits.userType() does not return userType" 162 | ); 163 | assertEquals( 164 | Double.valueOf(1.3), 165 | traits.staticIpScore(), 166 | "traits.staticIpScore() does not return 1.3" 167 | ); 168 | assertEquals( 169 | Integer.valueOf(2), 170 | traits.userCount(), 171 | "traits.userCount() does not return 2" 172 | ); 173 | assertEquals( 174 | Double.valueOf(0.01), 175 | traits.ipRiskSnapshot(), 176 | "traits.ipRiskSnapshot() does not return 0.01" 177 | ); 178 | } 179 | 180 | @Test 181 | public void testAnonymizer() { 182 | Anonymizer anonymizer = this.insights.anonymizer(); 183 | 184 | assertNotNull(anonymizer, "insights.anonymizer() returns null"); 185 | assertEquals( 186 | Integer.valueOf(99), 187 | anonymizer.confidence(), 188 | "anonymizer.confidence() does not return 99" 189 | ); 190 | assertTrue(anonymizer.isAnonymous(), "anonymizer.isAnonymous() returns true"); 191 | assertTrue(anonymizer.isAnonymousVpn(), "anonymizer.isAnonymousVpn() returns true"); 192 | assertTrue(anonymizer.isHostingProvider(), "anonymizer.isHostingProvider() returns true"); 193 | assertTrue(anonymizer.isPublicProxy(), "anonymizer.isPublicProxy() returns true"); 194 | assertTrue( 195 | anonymizer.isResidentialProxy(), 196 | "anonymizer.isResidentialProxy() returns true" 197 | ); 198 | assertTrue(anonymizer.isTorExitNode(), "anonymizer.isTorExitNode() returns true"); 199 | assertEquals( 200 | LocalDate.parse("2024-12-31"), 201 | anonymizer.networkLastSeen(), 202 | "anonymizer.networkLastSeen() does not return 2024-12-31" 203 | ); 204 | assertEquals( 205 | "NordVPN", 206 | anonymizer.providerName(), 207 | "anonymizer.providerName() does not return NordVPN" 208 | ); 209 | } 210 | 211 | @Test 212 | public void testLocation() { 213 | 214 | Location location = this.insights.location(); 215 | 216 | assertNotNull(location, "city.location() returns null"); 217 | 218 | assertEquals( 219 | Integer.valueOf(24626), 220 | location.averageIncome(), 221 | "location.averageIncome() does not return 24626" 222 | ); 223 | 224 | assertEquals( 225 | Integer.valueOf(1500), 226 | location.accuracyRadius(), 227 | "location.accuracyRadius() does not return 1500" 228 | ); 229 | 230 | double latitude = location.latitude(); 231 | assertEquals( 232 | 44.98, 233 | latitude, 234 | 0.1, 235 | "location.latitude() does not return 44.98" 236 | ); 237 | double longitude = location.longitude(); 238 | assertEquals( 239 | 93.2636, 240 | longitude, 241 | 0.1, 242 | "location.longitude() does not return 93.2636" 243 | ); 244 | assertEquals( 245 | Integer.valueOf(1341), 246 | location.populationDensity(), 247 | "location.populationDensity() does not return 1341" 248 | ); 249 | assertEquals( 250 | "America/Chicago", 251 | location.timeZone(), 252 | "location.timeZone() does not return America/Chicago" 253 | ); 254 | } 255 | 256 | @Test 257 | public void testMaxMind() { 258 | MaxMind maxmind = this.insights.maxmind(); 259 | assertEquals( 260 | 11, maxmind 261 | .queriesRemaining().intValue(), 262 | "Correct number of queries remaining" 263 | ); 264 | } 265 | 266 | @Test 267 | public void testPostal() { 268 | 269 | Postal postal = this.insights.postal(); 270 | assertEquals( 271 | "55401", 272 | postal.code(), 273 | "postal.code() does not return 55401" 274 | ); 275 | assertEquals( 276 | Integer.valueOf(33), 277 | postal.confidence(), 278 | "postal.confidence() does not return 33" 279 | ); 280 | } 281 | 282 | @Test 283 | public void testRepresentedCountry() { 284 | assertNotNull( 285 | this.insights.representedCountry(), 286 | "city.representedCountry() returns null" 287 | ); 288 | 289 | assertEquals( 290 | "C", 291 | this.insights.representedCountry().type(), 292 | "city.representedCountry().type() does not return C" 293 | ); 294 | assertTrue( 295 | this.insights.representedCountry().isInEuropeanUnion(), 296 | "city.representedCountry().isInEuropeanUnion() does not return true" 297 | ); 298 | } 299 | 300 | @Test 301 | public void testIsInEuropeanUnion() throws IOException, GeoIp2Exception { 302 | // This uses an alternate fixture where we have the 303 | // is_in_european_union flag set in locations not set in the other 304 | // fixture. 305 | WebServiceClient client = new WebServiceClient.Builder(6, "0123456789") 306 | .host("localhost") 307 | .port(wireMock.getPort()) 308 | .disableHttps() 309 | .build(); 310 | 311 | InsightsResponse insights = client.insights( 312 | InetAddress.getByName("1.1.1.2")); 313 | 314 | assertTrue( 315 | insights.country().isInEuropeanUnion(), 316 | "getCountry().isInEuropeanUnion() does not return true" 317 | ); 318 | assertTrue( 319 | insights.registeredCountry().isInEuropeanUnion(), 320 | "getRegisteredCountry().() isInEuropeanUnion = does not return true" 321 | ); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/main/java/com/maxmind/geoip2/model/CityResponse.java: -------------------------------------------------------------------------------- 1 | package com.maxmind.geoip2.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.maxmind.db.MaxMindDbParameter; 6 | import com.maxmind.geoip2.JsonSerializable; 7 | import com.maxmind.geoip2.record.City; 8 | import com.maxmind.geoip2.record.Continent; 9 | import com.maxmind.geoip2.record.Country; 10 | import com.maxmind.geoip2.record.Location; 11 | import com.maxmind.geoip2.record.MaxMind; 12 | import com.maxmind.geoip2.record.Postal; 13 | import com.maxmind.geoip2.record.RepresentedCountry; 14 | import com.maxmind.geoip2.record.Subdivision; 15 | import com.maxmind.geoip2.record.Traits; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * This class provides a model for the data returned by the City Plus web 21 | * service and the City database. 22 | * 23 | * @param city City record for the requested IP address. 24 | * @param continent Continent record for the requested IP address. 25 | * @param country Country record for the requested IP address. This object represents the country 26 | * where MaxMind believes the end user is located. 27 | * @param location Location record for the requested IP address. 28 | * @param maxmind MaxMind record containing data related to your account. 29 | * @param postal Postal record for the requested IP address. 30 | * @param registeredCountry Registered country record for the requested IP address. This record 31 | * represents the country where the ISP has registered a given IP block 32 | * and may differ from the user's country. 33 | * @param representedCountry Represented country record for the requested IP address. The 34 | * represented country is used for things like military bases. It is 35 | * only present when the represented country differs from the country. 36 | * @param subdivisions An {@link List} of {@link Subdivision} objects representing the country 37 | * subdivisions for the requested IP address. The number and type of 38 | * subdivisions varies by country, but a subdivision is typically a state, 39 | * province, county, etc. Subdivisions are ordered from most general (largest) 40 | * to most specific (smallest). If the response did not contain any 41 | * subdivisions, this is an empty list. 42 | * @param traits Record for the traits of the requested IP address. 43 | * @see GeoIP2 Web 44 | * Services 45 | */ 46 | public record CityResponse( 47 | @JsonProperty("city") 48 | @MaxMindDbParameter(name = "city") 49 | City city, 50 | 51 | @JsonProperty("continent") 52 | @MaxMindDbParameter(name = "continent") 53 | Continent continent, 54 | 55 | @JsonProperty("country") 56 | @MaxMindDbParameter(name = "country") 57 | Country country, 58 | 59 | @JsonProperty("location") 60 | @MaxMindDbParameter(name = "location") 61 | Location location, 62 | 63 | @JsonProperty("maxmind") 64 | @MaxMindDbParameter(name = "maxmind") 65 | MaxMind maxmind, 66 | 67 | @JsonProperty("postal") 68 | @MaxMindDbParameter(name = "postal") 69 | Postal postal, 70 | 71 | @JsonProperty("registered_country") 72 | @MaxMindDbParameter(name = "registered_country") 73 | Country registeredCountry, 74 | 75 | @JsonProperty("represented_country") 76 | @MaxMindDbParameter(name = "represented_country") 77 | RepresentedCountry representedCountry, 78 | 79 | @JsonProperty("subdivisions") 80 | @MaxMindDbParameter(name = "subdivisions") 81 | List subdivisions, 82 | 83 | @JsonProperty("traits") 84 | @MaxMindDbParameter(name = "traits") 85 | Traits traits 86 | ) implements JsonSerializable { 87 | 88 | /** 89 | * Compact canonical constructor that sets defaults for null values. 90 | */ 91 | public CityResponse { 92 | city = city != null ? city : new City(); 93 | continent = continent != null ? continent : new Continent(); 94 | country = country != null ? country : new Country(); 95 | location = location != null ? location : new Location(); 96 | maxmind = maxmind != null ? maxmind : new MaxMind(); 97 | postal = postal != null ? postal : new Postal(); 98 | registeredCountry = registeredCountry != null ? registeredCountry : new Country(); 99 | representedCountry = representedCountry != null 100 | ? representedCountry : new RepresentedCountry(); 101 | subdivisions = subdivisions != null ? List.copyOf(subdivisions) : List.of(); 102 | traits = traits != null ? traits : new Traits(); 103 | } 104 | 105 | /** 106 | * Constructs an instance of {@code CityResponse} with the specified parameters. 107 | * 108 | * @param response the response 109 | * @param locales the locales 110 | */ 111 | public CityResponse( 112 | CityResponse response, 113 | List locales 114 | ) { 115 | this( 116 | new City(response.city(), locales), 117 | new Continent(response.continent(), locales), 118 | new Country(response.country(), locales), 119 | response.location(), 120 | response.maxmind(), 121 | response.postal(), 122 | new Country(response.registeredCountry(), locales), 123 | new RepresentedCountry(response.representedCountry(), locales), 124 | mapSubdivisions(response.subdivisions(), locales), 125 | response.traits() 126 | ); 127 | } 128 | 129 | private static ArrayList mapSubdivisions( 130 | List subdivisions, 131 | List locales 132 | ) { 133 | var subdivisions2 = new ArrayList(subdivisions.size()); 134 | for (var subdivision : subdivisions) { 135 | subdivisions2.add(new Subdivision(subdivision, locales)); 136 | } 137 | return subdivisions2; 138 | } 139 | 140 | /** 141 | * @return City record for the requested IP address. 142 | * @deprecated Use {@link #city()} instead. This method will be removed in 6.0.0. 143 | */ 144 | @Deprecated(since = "5.0.0", forRemoval = true) 145 | public City getCity() { 146 | return city(); 147 | } 148 | 149 | /** 150 | * @return Continent record for the requested IP address. 151 | * @deprecated Use {@link #continent()} instead. This method will be removed in 6.0.0. 152 | */ 153 | @Deprecated(since = "5.0.0", forRemoval = true) 154 | public Continent getContinent() { 155 | return continent(); 156 | } 157 | 158 | /** 159 | * @return Country record for the requested IP address. This object 160 | * represents the country where MaxMind believes the end user is 161 | * located. 162 | * @deprecated Use {@link #country()} instead. This method will be removed in 6.0.0. 163 | */ 164 | @Deprecated(since = "5.0.0", forRemoval = true) 165 | public Country getCountry() { 166 | return country(); 167 | } 168 | 169 | /** 170 | * @return Location record for the requested IP address. 171 | * @deprecated Use {@link #location()} instead. This method will be removed in 6.0.0. 172 | */ 173 | @Deprecated(since = "5.0.0", forRemoval = true) 174 | public Location getLocation() { 175 | return location(); 176 | } 177 | 178 | /** 179 | * @return MaxMind record containing data related to your account. 180 | * @deprecated Use {@link #maxmind()} instead. This method will be removed in 6.0.0. 181 | */ 182 | @Deprecated(since = "5.0.0", forRemoval = true) 183 | @JsonProperty("maxmind") 184 | public MaxMind getMaxMind() { 185 | return maxmind(); 186 | } 187 | 188 | /** 189 | * @return the postal 190 | * @deprecated Use {@link #postal()} instead. This method will be removed in 6.0.0. 191 | */ 192 | @Deprecated(since = "5.0.0", forRemoval = true) 193 | public Postal getPostal() { 194 | return postal(); 195 | } 196 | 197 | /** 198 | * @return Registered country record for the requested IP address. This 199 | * record represents the country where the ISP has registered a 200 | * given IP block and may differ from the user's country. 201 | * @deprecated Use {@link #registeredCountry()} instead. This method will be removed in 6.0.0. 202 | */ 203 | @Deprecated(since = "5.0.0", forRemoval = true) 204 | @JsonProperty("registered_country") 205 | public Country getRegisteredCountry() { 206 | return registeredCountry(); 207 | } 208 | 209 | /** 210 | * @return Represented country record for the requested IP address. The 211 | * represented country is used for things like military bases. It is 212 | * only present when the represented country differs from the 213 | * country. 214 | * @deprecated Use {@link #representedCountry()} instead. This method will be removed in 6.0.0. 215 | */ 216 | @Deprecated(since = "5.0.0", forRemoval = true) 217 | @JsonProperty("represented_country") 218 | public RepresentedCountry getRepresentedCountry() { 219 | return representedCountry(); 220 | } 221 | 222 | /** 223 | * @return An {@link List} of {@link Subdivision} objects representing the 224 | * country subdivisions for the requested IP address. The number and 225 | * type of subdivisions varies by country, but a subdivision is 226 | * typically a state, province, county, etc. Subdivisions are 227 | * ordered from most general (largest) to most specific (smallest). 228 | * If the response did not contain any subdivisions, this method 229 | * returns an empty array. 230 | * @deprecated Use {@link #subdivisions()} instead. This method will be removed in 6.0.0. 231 | */ 232 | @Deprecated(since = "5.0.0", forRemoval = true) 233 | public List getSubdivisions() { 234 | return new ArrayList<>(subdivisions()); 235 | } 236 | 237 | /** 238 | * @return Record for the traits of the requested IP address. 239 | * @deprecated Use {@link #traits()} instead. This method will be removed in 6.0.0. 240 | */ 241 | @Deprecated(since = "5.0.0", forRemoval = true) 242 | public Traits getTraits() { 243 | return traits(); 244 | } 245 | 246 | /** 247 | * @return An object representing the most specific subdivision returned. If 248 | * the response did not contain any subdivisions, this method 249 | * returns an empty {@link Subdivision} object. 250 | */ 251 | @JsonIgnore 252 | public Subdivision mostSpecificSubdivision() { 253 | if (subdivisions().isEmpty()) { 254 | return new Subdivision(); 255 | } 256 | return subdivisions().get(subdivisions().size() - 1); 257 | } 258 | 259 | /** 260 | * @return An object representing the most specific subdivision returned. If 261 | * the response did not contain any subdivisions, this method 262 | * returns an empty {@link Subdivision} object. 263 | * @deprecated Use {@link #mostSpecificSubdivision()} instead. This method will be removed 264 | * in 6.0.0. 265 | */ 266 | @JsonIgnore 267 | @Deprecated(since = "5.0.0", forRemoval = true) 268 | public Subdivision getMostSpecificSubdivision() { 269 | return mostSpecificSubdivision(); 270 | } 271 | 272 | /** 273 | * @return An object representing the least specific subdivision returned. If 274 | * the response did not contain any subdivisions, this method 275 | * returns an empty {@link Subdivision} object. 276 | */ 277 | @JsonIgnore 278 | public Subdivision leastSpecificSubdivision() { 279 | if (subdivisions().isEmpty()) { 280 | return new Subdivision(); 281 | } 282 | return subdivisions().get(0); 283 | } 284 | 285 | /** 286 | * @return An object representing the least specific subdivision returned. If 287 | * the response did not contain any subdivisions, this method 288 | * returns an empty {@link Subdivision} object. 289 | * @deprecated Use {@link #leastSpecificSubdivision()} instead. This method will be removed 290 | * in 6.0.0. 291 | */ 292 | @JsonIgnore 293 | @Deprecated(since = "5.0.0", forRemoval = true) 294 | public Subdivision getLeastSpecificSubdivision() { 295 | return leastSpecificSubdivision(); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.maxmind.geoip2 5 | geoip2 6 | 5.0.2 7 | jar 8 | MaxMind GeoIP2 API 9 | GeoIP2 webservice client and database reader 10 | https://dev.maxmind.com/geoip?lang=en 11 | 12 | 13 | Apache License, Version 2.0 14 | https://www.apache.org/licenses/LICENSE-2.0.html 15 | repo 16 | 17 | 18 | 19 | MaxMind, Inc. 20 | https://www.maxmind.com/ 21 | 22 | 23 | https://github.com/maxmind/GeoIP2-java 24 | scm:git:git://github.com:maxmind/GeoIP2-java.git 25 | scm:git:git@github.com:maxmind/GeoIP2-java.git 26 | HEAD 27 | 28 | 29 | https://github.com/maxmind/GeoIP2-java/issues 30 | GitHub 31 | 32 | 33 | 34 | oschwald 35 | Gregory J. Oschwald 36 | goschwald@maxmind.com 37 | 38 | 39 | 40 | 41 | Central Portal Snapshots 42 | central-portal-snapshots 43 | https://central.sonatype.com/repository/maven-snapshots/ 44 | 45 | false 46 | 47 | 48 | true 49 | 50 | 51 | 52 | 53 | 54 | com.maxmind.db 55 | maxmind-db 56 | 4.0.2 57 | 58 | 59 | com.fasterxml.jackson.core 60 | jackson-databind 61 | 2.20.1 62 | 63 | 64 | com.fasterxml.jackson.datatype 65 | jackson-datatype-jsr310 66 | 2.20.1 67 | 68 | 69 | com.fasterxml.jackson.core 70 | jackson-core 71 | 2.20.1 72 | 73 | 74 | com.fasterxml.jackson.core 75 | jackson-annotations 76 | 2.20 77 | 78 | 79 | org.wiremock 80 | wiremock 81 | 3.13.2 82 | test 83 | 84 | 85 | com.fasterxml.jackson.jr 86 | jackson-jr-objects 87 | 2.20.1 88 | test 89 | 90 | 91 | com.jcabi 92 | jcabi-matchers 93 | 1.8.0 94 | test 95 | 96 | 97 | org.junit.jupiter 98 | junit-jupiter 99 | 6.0.1 100 | test 101 | 102 | 103 | 104 | UTF-8 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-enforcer-plugin 111 | 3.6.2 112 | 113 | 114 | enforce-maven 115 | 116 | enforce 117 | 118 | 119 | 120 | 121 | 3.6.3 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-checkstyle-plugin 131 | 3.6.0 132 | 133 | true 134 | checkstyle.xml 135 | checkstyle-suppressions.xml 136 | warning 137 | 138 | 139 | 140 | com.puppycrawl.tools 141 | checkstyle 142 | 12.2.0 143 | 144 | 145 | 146 | 147 | test 148 | test 149 | 150 | check 151 | 152 | 153 | 154 | 155 | 156 | maven-javadoc-plugin 157 | 3.12.0 158 | 159 | 160 | 161 | https://maxmind.github.io/MaxMind-DB-Reader-java/doc/latest/ 162 | 163 | 164 | 17 165 | -missing 166 | 167 | 168 | 169 | javadoc-jar 170 | package 171 | 172 | jar 173 | 174 | 175 | 176 | 177 | 178 | org.apache.maven.plugins 179 | maven-assembly-plugin 180 | 3.8.0 181 | 182 | 183 | src/assembly/bin.xml 184 | 185 | 186 | 187 | 188 | package 189 | 190 | single 191 | 192 | 193 | 194 | 195 | 196 | org.apache.maven.plugins 197 | maven-gpg-plugin 198 | 3.2.8 199 | 200 | 201 | sign-artifacts 202 | verify 203 | 204 | sign 205 | 206 | 207 | 208 | 209 | 210 | org.apache.maven.plugins 211 | maven-compiler-plugin 212 | 3.14.1 213 | 214 | 17 215 | 17 216 | 11 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-jar-plugin 222 | 3.5.0 223 | true 224 | 225 | 226 | 227 | true 228 | true 229 | 230 | 231 | 232 | 233 | 234 | org.apache.maven.plugins 235 | maven-source-plugin 236 | 3.4.0 237 | 238 | 239 | attach-sources 240 | package 241 | 242 | jar-no-fork 243 | 244 | 245 | 246 | 247 | 248 | org.apache.maven.plugins 249 | maven-surefire-plugin 250 | 3.5.4 251 | 252 | 253 | org.codehaus.mojo 254 | versions-maven-plugin 255 | 2.20.1 256 | 257 | 258 | org.sonatype.central 259 | central-publishing-maven-plugin 260 | 0.9.0 261 | true 262 | 263 | central 264 | true 265 | 266 | 267 | 268 | 269 | 270 | 271 | not-windows 272 | 278 | 279 | !Windows 280 | 281 | 282 | 283 | 284 | 289 | org.codehaus.mojo 290 | exec-maven-plugin 291 | 3.6.2 292 | 293 | 294 | initialize 295 | invoke build 296 | 297 | exec 298 | 299 | 300 | 301 | 302 | git 303 | submodule update --init --recursive 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | --------------------------------------------------------------------------------