This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
mvn testmvn test -Dtest=WebServiceClientTestmvn test -Dtest=WebServiceClientTest#testFullScoreTransactionmvn packagemvn clean packagemvn checkstyle:checkCheckstyle runs automatically during the test phase and enforces Google Java Style conventions.
mvn javadoc:javadocWebServiceClient (src/main/java/com/maxmind/minfraud/WebServiceClient.java)
- Main entry point for the API
- Provides methods:
score(),insights(),factors(), andreportTransaction() - Uses Java's built-in
HttpClientfor HTTP communication - Thread-safe and designed to be reused across multiple requests for connection pooling
- Handles authentication via Basic Auth headers
- Supports configurable timeouts, proxies, and custom
HttpClientinstances
Request Models (src/main/java/com/maxmind/minfraud/request/)
- All request classes extend
AbstractModel - Built using the Builder pattern (e.g.,
Transaction.Builder,Device.Builder) Transactionis the primary request object composed of multiple optional sub-models:Account,Billing,CreditCard,Device,Email,Event,Order,Payment,ShippingShoppingCartItem(can have multiple)CustomInputs(for custom key-value pairs)
- All request models serialize to JSON via
toJson()method - Immutable after construction
Response Models (src/main/java/com/maxmind/minfraud/response/)
- Three main response types:
ScoreResponse,InsightsResponse,FactorsResponse - Use Java records (as of version 4.0.0) with deprecated getter methods for backwards compatibility
- Implement
JsonSerializableinterface InsightsResponseandFactorsResponseextendScoreResponsewith additional fields- Response models include GeoIP2 data (this library depends on
com.maxmind.geoip2:geoip2)
Exception Hierarchy (src/main/java/com/maxmind/minfraud/exception/)
MinFraudException(base checked exception)AuthenticationExceptionInsufficientFundsExceptionInvalidRequestExceptionPermissionRequiredException
HttpException(for unexpected HTTP errors)
JSON Handling
- Uses Jackson for serialization/deserialization
- Centralized
Mapperclass provides configuredObjectMapperinstance - JSON property names use snake_case (e.g.,
risk_score,ip_address) @JsonPropertyannotations map between camelCase Java and snake_case JSON
- Builder Pattern: All request models use nested Builder classes for object construction
- Immutability: Request models are immutable after construction; response models use records
- Composition: The
Transactionclass composes multiple optional sub-models - Thread Safety:
WebServiceClientis thread-safe and should be reused (enables connection pooling) - Empty Object Defaults: Response models return empty objects instead of null for better API ergonomics
This project requires Java 17+. Use modern Java features appropriately:
- Records for immutable data models
varfor local variables when type is obvious- Switch expressions
- Text blocks for multi-line strings
- Pattern matching where applicable
The project uses Google Java Style with Checkstyle enforcement (see checkstyle.xml):
- 4 spaces for indentation (no tabs)
- 100 character line length limit
- Opening braces on same line
- Member names: camelCase starting with lowercase (minimum 2 characters)
- Parameters: camelCase (single letter allowed)
- Constants: UPPER_SNAKE_CASE
- No star imports
- Variables should be declared close to where they're used
- No abbreviations in names except standard ones (e.g., ID, IP, URI, URL, JSON)
Run mvn checkstyle:check to verify compliance before committing.
- All public classes, methods, and constructors require Javadoc
- Public fields require Javadoc
- Use
@param,@return,@throwstags appropriately - Records should document parameters in the record declaration
- Include examples in Javadoc where helpful
Record parameters are always ordered alphabetically by field name for consistency:
public record ScoreResponse(
Disposition disposition, // D
Double fundsRemaining, // F
UUID id, // I
ScoreIpAddress ipAddress, // I (after "id")
Integer queriesRemaining, // Q
Double riskScore, // R
List<Warning> warnings // W
) implements JsonSerializable {
// ...
}Use compact canonical constructors to set defaults and ensure non-null values:
public record ScoreResponse(...) {
public ScoreResponse {
disposition = disposition != null ? disposition : new Disposition();
ipAddress = ipAddress != null ? ipAddress : new ScoreIpAddress();
warnings = warnings != null ? List.copyOf(warnings) : List.of();
}
}This ensures users can safely call methods without null checks: response.disposition().action().
Response model enums use a simple, forward-compatible pattern that gracefully handles unknown values from the server.
Pattern:
public enum Status {
LIVE,
PARKED,
DNS_ERROR;
@Override
public String toString() {
return name().toLowerCase();
}
}How it works:
- Enum constants use
UPPER_SNAKE_CASE(e.g.,DNS_ERROR,ISP_EMAIL) - Override
toString()to returnname().toLowerCase()for JSON serialization - The
Mapperclass configures Jackson with:READ_ENUMS_USING_TO_STRING- Deserializes usingtoString()READ_UNKNOWN_ENUM_VALUES_AS_NULL- Unknown values becomenullinstead of throwing exceptions
Example:
// Serialization: DNS_ERROR → "dns_error"
Status status = Status.DNS_ERROR;
System.out.println(status); // "dns_error"
// Deserialization: "dns_error" → DNS_ERROR
// Unknown: "future_value" → null (no exception!)When to use:
- Use enums for response fields with fixed/enumerated values
- Use String for open-ended text fields
- Request enums use the same pattern but don't need forward compatibility concerns
Do NOT add deprecated getter methods for new fields. Deprecated getters only exist for backward compatibility with fields that had JavaBeans-style getters before the record migration in version 4.0.0.
When deprecating existing fields:
public record Response(
@Deprecated(since = "4.x.0", forRemoval = true)
@JsonProperty("old_field")
String oldField,
@JsonProperty("new_field")
String newField
) {
// The record accessor oldField() is automatically marked as deprecated
// Keep the old deprecated getter ONLY if it existed before v4.0.0
@Deprecated(since = "4.x.0", forRemoval = true)
public String getOldField() {
return oldField();
}
}Include helpful deprecation messages in JavaDoc pointing to alternatives.
When adding a new field to a record during a minor version release (e.g., 4.1.0 → 4.2.0), you must maintain backward compatibility for code that constructs records directly.
The Problem: Adding a field to a record changes the canonical constructor signature, breaking existing code.
The Solution: Add a deprecated constructor matching the old signature:
public record Email(
@JsonProperty("address")
String address,
@JsonProperty("domain")
String domain,
// NEW FIELD added in minor version 4.2.0 (inserted alphabetically between "domain" and "first_seen")
@JsonProperty("domain_last_seen")
LocalDate domainLastSeen,
@JsonProperty("first_seen")
LocalDate firstSeen
) {
// Updated default constructor with new field
public Email() {
this(null, null, null, null);
}
// Deprecated constructor maintaining old signature for backward compatibility
@Deprecated(since = "4.2.0", forRemoval = true)
public Email(
String address,
String domain,
LocalDate firstSeen
) {
// Call new constructor with null for the new field (in alphabetical position)
this(address, domain, null, firstSeen);
}
}For Major Versions (e.g., 4.x → 5.0): Skip the deprecated constructor—breaking changes are expected.
Update CHANGELOG.md when adding fields:
## 4.2.0 (2024-xx-xx)
* A new `domainLastSeen` field has been added to the `Email` response object...- Tests use JUnit 5 (Jupiter)
- Tests use WireMock for HTTP mocking
- Test methods should have descriptive names starting with
test - Use static imports for assertions and matchers
- Full request/response examples in test resources:
src/test/resources/ - Update test JSON fixtures when adding response fields
- Verify proper serialization/deserialization in tests
Both request and response classes are immutable and thread-safe. WebServiceClient is explicitly designed to be thread-safe and should be reused:
// Good: Create once, share across threads
WebServiceClient client = new WebServiceClient.Builder(accountId, licenseKey).build();
// Use in multiple threads
executor.submit(() -> client.score(transaction1));
executor.submit(() -> client.score(transaction2));Reusing the client enables connection pooling and improves performance.
- Add the field to the appropriate request class in
request/package - Follow the Builder pattern used by other request classes:
public Builder fieldName(Type val) { fieldName = val; return this; }
- Add proper Javadoc and
@JsonPropertyannotation with snake_case name - Add validation in the builder if needed (e.g., throw
IllegalArgumentException) - Update corresponding test class in
src/test/java/ - Add test JSON examples in
src/test/resources/
- Determine alphabetical position for the new field
- Add to the record parameters with
@JsonProperty:@JsonProperty("field_name") Type fieldName,
- For minor version releases: Add a deprecated constructor matching the old signature (see "Avoiding Breaking Changes")
- Handle null values in the compact canonical constructor if needed
- Do NOT add a deprecated getter for the new field
- Add JavaDoc describing the field
- Update test fixtures (
src/test/resources/) with example data - Add test assertions to verify proper deserialization
- Update CHANGELOG.md
When creating an entirely new record class in response/:
- Use Java record syntax
- Alphabetize parameters by field name
- Add
@JsonPropertyannotations for all fields - Implement
JsonSerializableinterface - Add a compact canonical constructor to set defaults for null values
- Provide comprehensive JavaDoc for all parameters
- Do NOT add deprecated getters (only needed for legacy compatibility)
- WireMock runs on dynamic ports in tests (
@RegisterExtensionwithdynamicPort()) - Check
WebServiceClientfor HTTP request construction - Response body must be fully consumed even on errors (see
exhaustBody()method) - Error responses (4xx) include
codeanderrorfields in JSON body - Use
verifyRequestFor()helper in tests to check sent JSON
Adding a new field to a record changes the canonical constructor signature, breaking existing code.
Solution: For minor version releases, add a deprecated constructor that maintains the old signature. See "Avoiding Breaking Changes in Minor Versions" section for details.
When you have two constructors with similar signatures, you may get "ambiguous constructor" errors.
Solution: Cast null parameters to their specific type:
this((String) null, (LocalDate) null, (Integer) null);After adding new fields to a response model, tests fail with deserialization errors.
Solution: Update all related test fixtures:
- Test JSON files (e.g.,
score-response.json,insights-response.json) - In-line JSON in test classes
- Test assertions in
*ResponseTest.javafiles
Code style violations prevent merging.
Solution: Run mvn checkstyle:check regularly during development. Common issues:
- Lines exceeding 100 characters
- Missing Javadoc on public methods
- Incorrect indentation (use 4 spaces)
- Star imports
Use to set defaults and ensure non-null values:
public record InsightsResponse(...) {
public InsightsResponse {
// Ensure non-null with empty defaults
disposition = disposition != null ? disposition : new Disposition();
ipAddress = ipAddress != null ? ipAddress : new IpAddress();
warnings = warnings != null ? List.copyOf(warnings) : List.of();
}
}Return empty objects instead of null for better API ergonomics:
// Users can safely call without null checks
String action = response.disposition().action(); // Works even if disposition is "empty"All models implement JsonSerializable for consistent JSON output:
public interface JsonSerializable {
default String toJson() throws IOException {
return Mapper.get().writeValueAsString(this);
}
}Usage:
InsightsResponse response = client.insights(transaction);
String json = response.toJson(); // Pretty-printed JSON outputThe project depends on:
- Jackson (core, databind, annotations, datatype-jsr310): JSON handling
- GeoIP2 Java API: Sibling library providing GeoIP2 models used in responses
- JUnit 5 (Jupiter): Testing framework
- WireMock: HTTP mocking for tests
- jsonassert: JSON comparison in tests
When updating dependencies, test thoroughly as noted in README.dev.md.
See README.dev.md for detailed release instructions. Key points:
- Releases require GPG signing
- Requires access to Central Portal (formerly Sonatype)
- Release script is
./dev-bin/release.sh - Update
CHANGELOG.mdbefore releasing with version and date - Review open issues and PRs before releasing
- Bump copyright year in
README.mdif appropriate