Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: ['8', '11', '17', '21']

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'

- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-

- name: Run tests
run: mvn clean test

- name: Build project
run: mvn clean package

- name: Generate JavaDoc
run: mvn javadoc:javadoc

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-java-${{ matrix.java-version }}
path: target/surefire-reports/

code-quality:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 8
uses: actions/setup-java@v4
with:
java-version: '8'
distribution: 'temurin'

- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-

- name: Check for dependency updates
run: mvn versions:display-dependency-updates
144 changes: 144 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
name: Release to Maven Central

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., 1.5.4)'
required: true
type: string

jobs:
release:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.set-version.outputs.version }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 8
uses: actions/setup-java@v4
with:
java-version: '8'
distribution: 'temurin'

- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-

- name: Import GPG key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --batch --import
echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf
echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf
gpg --list-secret-keys --keyid-format LONG

- name: Configure Maven settings
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
run: |
mkdir -p ~/.m2
cat > ~/.m2/settings.xml << EOF
<settings>
<servers>
<server>
<id>central</id>
<username>${MAVEN_CENTRAL_USERNAME}</username>
<password>${MAVEN_CENTRAL_TOKEN}</password>
</server>
</servers>
</settings>
EOF

- name: Set version from tag
id: set-version
if: startsWith(github.ref, 'refs/tags/')
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false

- name: Set version from input
id: set-version-input
if: github.event_name == 'workflow_dispatch'
run: |
VERSION=${{ github.event.inputs.version }}
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false

- name: Run tests
run: mvn clean test

- name: Deploy to Maven Central
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
mvn clean deploy -Dgpg.passphrase=$GPG_PASSPHRASE

- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const releaseBody = `## Mixpanel Java SDK v${process.env.VERSION}

### Maven
\`\`\`xml
<dependency>
<groupId>com.mixpanel</groupId>
<artifactId>mixpanel-java</artifactId>
<version>${process.env.VERSION}</version>
</dependency>
\`\`\`

### Changes
See [CHANGELOG](https://github.com/mixpanel/mixpanel-java/blob/master/CHANGELOG.md) for details.

### Links
- [Maven Central](https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java/${process.env.VERSION})
- [JavaDoc](http://mixpanel.github.io/mixpanel-java/)`;

await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: context.ref.replace('refs/tags/', ''),
name: `Release ${process.env.VERSION}`,
body: releaseBody,
draft: false,
prerelease: false
});

verify:
needs: release
runs-on: ubuntu-latest
if: success()

steps:
- name: Wait for Maven Central sync
run: sleep 300 # Wait 5 minutes for synchronization

- name: Verify artifact on Maven Central
run: |
VERSION=${{ needs.release.outputs.version }}
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The release job doesn't define any outputs, so needs.release.outputs.version will be undefined. The VERSION should be retrieved from the environment variable set in the release job or passed as a job output.

Copilot uses AI. Check for mistakes.
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://repo1.maven.org/maven2/com/mixpanel/mixpanel-java/${VERSION}/mixpanel-java-${VERSION}.jar)
if [ $RESPONSE -eq 200 ]; then
echo "✅ Artifact successfully published to Maven Central"
else
echo "⚠️ Artifact not yet available on Maven Central (HTTP $RESPONSE). This is normal - it may take up to 30 minutes to appear."
fi
158 changes: 158 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is the official Mixpanel tracking library for Java - a production-ready library for sending analytics events and user profile updates to Mixpanel from Java server-side applications.

## Release Process

### Quick Commands for Releases

```bash
# 1. Update version (remove -SNAPSHOT from pom.xml)
mvn versions:set -DnewVersion=1.5.4

# 2. Run tests
mvn clean test

# 3. Deploy to Maven Central Portal
mvn clean deploy

# 4. After release, prepare next version
mvn versions:set -DnewVersion=1.5.5-SNAPSHOT
```

### Key Files
- **RELEASE.md**: Complete release documentation with step-by-step instructions
- **.github/workflows/release.yml**: Automated release workflow triggered by version tags
- **.github/workflows/ci.yml**: Continuous integration for all PRs and master commits

### Maven Central Portal
- The project uses the new Maven Central Portal (not the deprecated OSSRH)
- Deployments are visible at: https://central.sonatype.com/publishing/deployments
- Published artifacts: https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java

### Required GitHub Secrets for CI/CD
- `GPG_PRIVATE_KEY`: Base64-encoded GPG private key
- `GPG_PASSPHRASE`: GPG key passphrase
- `MAVEN_CENTRAL_USERNAME`: Maven Central Portal username
- `MAVEN_CENTRAL_TOKEN`: Maven Central Portal token

## Build and Development Commands

```bash
# Build the project and create JAR
mvn clean package

# Run all tests
mvn test

# Run a specific test class
mvn test -Dtest=MixpanelAPITest

# Run a specific test method
mvn test -Dtest=MixpanelAPITest#testBuildEventMessage

# Install to local Maven repository
mvn install

# Generate JavaDoc
mvn javadoc:javadoc

# Clean build artifacts
mvn clean

# Run the demo application (after building)
java -cp target/mixpanel-java-1.5.3.jar:target/classes:lib/json-20231013.jar com.mixpanel.mixpanelapi.demo.MixpanelAPIDemo <YOUR_MIXPANEL_TOKEN>
```

## High-Level Architecture

### Core Design Pattern
The library implements a **Producer-Consumer** pattern with intentional thread separation:

1. **Message Production** (`MessageBuilder`): Creates properly formatted JSON messages on application threads
2. **Message Batching** (`ClientDelivery`): Collects messages into efficient batches (max 50 per request)
3. **Message Transmission** (`MixpanelAPI`): Sends batched messages to Mixpanel servers

This separation allows for flexible threading models - the library doesn't impose any specific concurrency pattern, letting applications control their own threading strategy.

### Key Architectural Decisions

**No Built-in Threading**: Unlike some analytics libraries, this one doesn't start background threads. Applications must manage their own async patterns, as demonstrated in `MixpanelAPIDemo` which uses a `ConcurrentLinkedQueue` with producer/consumer threads.

**Message Format Validation**: `MessageBuilder` performs validation during message construction, throwing `MixpanelMessageException` for malformed data before network transmission.

**Batch Encoding**: Messages are JSON-encoded, then Base64-encoded, then URL-encoded for HTTP POST transmission. This triple encoding ensures compatibility with Mixpanel's API requirements.

**Network Communication**: Uses Java's built-in `java.net.URL` and `URLConnection` classes - no external HTTP client dependencies. Connection timeout is 2 seconds, read timeout is 10 seconds.

### Message Types and Endpoints

The library supports three message categories, each sent to different endpoints:

- **Events** (`/track`): User actions and behaviors
- **People** (`/engage`): User profile updates (set, increment, append, etc.)
- **Groups** (`/groups`): Group profile updates

Each message type has specific JSON structure requirements validated by `MessageBuilder`.

## Package Structure

All production code is in the `com.mixpanel.mixpanelapi` package:

- `MixpanelAPI`: HTTP communication with Mixpanel servers
- `MessageBuilder`: Constructs and validates JSON messages
- `ClientDelivery`: Batches multiple messages for efficient transmission
- `Config`: Contains API endpoints and configuration constants
- `Base64Coder`: Base64 encoding utility (modified third-party code)
- `MixpanelMessageException`: Runtime exception for message format errors
- `MixpanelServerException`: IOException for server rejection responses

## Testing Approach

Tests extend JUnit 4's `TestCase` and are located in `MixpanelAPITest`. The test suite covers:

- Message format validation for all message types
- Property operations (set, setOnce, increment, append, union, remove, unset)
- Large batch delivery behavior
- Encoding verification
- Error conditions and exception handling

When adding new functionality, follow the existing test patterns - each message type operation has corresponding test methods that verify both the JSON structure and the encoded format.

## Common Development Tasks

### Adding a New Message Type
1. Add the message construction method to `MessageBuilder`
2. Validate required fields and structure
3. Add corresponding tests in `MixpanelAPITest`
4. Update `ClientDelivery` if special handling is needed

### Modifying Network Behavior
Network configuration is centralized in `MixpanelAPI.sendData()`. Connection and read timeouts are hardcoded but could be made configurable by modifying the `Config` class.

### Debugging Failed Deliveries
The library throws `MixpanelServerException` with the HTTP response code and server message. Check:
1. Token validity in `MessageBuilder` constructor
2. Message size (batches limited to 50 messages)
3. JSON structure using the test suite patterns

## Dependencies

The library has minimal dependencies:
- **Production**: `org.json:json:20231013` for JSON manipulation
- **Test**: `junit:junit:4.13.2` for unit testing
- **Java Version**: Requires Java 8 or higher

## API Patterns to Follow

When working with this codebase:

1. **Immutable Messages**: Once created by `MessageBuilder`, JSON messages should not be modified
2. **Fail Fast**: Validate message structure early in `MessageBuilder` rather than during transmission
3. **Preserve Thread Safety**: `MessageBuilder` instances are NOT thread-safe; create one per thread
4. **Batch Appropriately**: `ClientDelivery` handles batching; don't exceed 50 messages per delivery
5. **Exception Handling**: Distinguish between `MixpanelMessageException` (client error) and `MixpanelServerException` (server error)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Latest Version
</dependency>
```

You can alternatively download the library jar directly from Maven [here](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.mixpanel%22%20AND%20a%3A%22mixpanel-java%22).
You can alternatively download the library jar directly from Maven Central [here](https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java).

How To Use
----------
Expand Down
Loading
Loading