diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index e39a8b3c..00000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,31 +0,0 @@
-*Before creating a new issue, make sure that you checked: (1) existing issues, (2) docs (3) FAQ.
-TEMPLATE FOR ISSUES ONLY - for feature requests just delete the text and start fresh.
-Please help us to fix issues asap: this template reduces checking with you back and forth.
-Replace italic sections with your input.
-For __support questions__, please use [stackoverflow with the [objectbox] tag](https://stackoverflow.com/questions/tagged/objectbox) - we get notified.*
-
-## Issue Basics
-- ObjectBox version (are using the latest version?): *?*
-- Reproducibility: *[occurred once only | occasionally without visible pattern | always]*
-
-## Reproducing the bug
-### Description
-*Describe the situation in English in which you encounter the bug.*
-
-### Code
-*Provide the code triggering the bug.
-Ideally, try to keep it short by removing as much code as possible while preserving the issue.
-You get __extra karma points__ for sharing a runnable project on GitHub that reproduces the bug.*
-
-### Logs & stackstraces
-*Check if you have relevant logs and/or a stacktrace.
-For __build issues__, use `--stacktrace` for the Gradle build (`./gradlew build --stacktrace`).
-For __runtime errors__, check Android's Logcat (check also for relevant logs preceeding the issue itself).*
-
-## Entities
-*Provide the code for entities related to the bug (shorten to relevant parts).*
-
-## Misc
-*Is there anything special about your app?
-May transactions or multi-threading play a role?
-Did you find any workarounds to prevent the issue?*
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..ebbfb89e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,98 @@
+---
+name: Bug report
+about: You found a bug in ObjectBox causing an application to crash or throw an exception, or something does not work right.
+title: ''
+labels: 'bug'
+assignees: ''
+
+---
+
+
+
+### Is there an existing issue?
+
+- [ ] I have searched [existing issues](https://github.com/objectbox/objectbox-java/issues)
+
+### Build info
+
+- ObjectBox version: [e.g. 3.7.0]
+- OS: [e.g. Android 14 | Ubuntu 22.04 | Windows 11 ]
+- Device/ABI/architecture: [e.g. Galaxy S23 | arm64-v8a | x86-64 ]
+
+### Steps to reproduce
+
+_TODO Tell us exactly how to reproduce the problem._
+
+1. ...
+2. ...
+3. ...
+
+### Expected behavior
+
+_TODO Tell us what you expect to happen._
+
+### Actual behavior
+
+_TODO Tell us what actually happens._
+
+
+### Code
+
+_TODO Add a code example to help us reproduce your problem._
+
+
+
+Code
+
+```java
+[Paste your code here]
+```
+
+
+
+### Logs, stack traces
+
+_TODO Add relevant logs, a stack trace or crash report._
+
+
+
+Logs
+
+```console
+[Paste your logs here]
+```
+
+
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..2aac0df1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Question
+ url: https://stackoverflow.com/questions/tagged/objectbox
+ about: Ask how to do something, or why it isn't working on Stack Overflow.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..1846a02e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,37 @@
+---
+name: Feature request
+about: Suggest an improvement for ObjectBox.
+title: ''
+labels: 'enhancement'
+assignees: ''
+
+---
+
+
+
+### Is there an existing issue?
+
+- [ ] I have searched [existing issues](https://github.com/objectbox/objectbox-java/issues)
+
+### Use case
+
+_TODO Describe what problem you are trying to solve._
+
+### Proposed solution
+
+_TODO Describe what you want to be able to do with ObjectBox._
+
+### Alternatives
+
+_TODO Describe any alternative solutions or features you've considered._
+
+### Additional context
+
+_TODO Add any other context (e.g. platform or language) about the feature request here._
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..2c431b0b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/close-no-response.yml b/.github/workflows/close-no-response.yml
new file mode 100644
index 00000000..3e906671
--- /dev/null
+++ b/.github/workflows/close-no-response.yml
@@ -0,0 +1,26 @@
+name: Close inactive issues
+on:
+ schedule:
+ - cron: "15 1 * * *" # “At 01:15.”
+ workflow_dispatch: # To support running manually.
+
+# Minimal access by default
+permissions:
+ contents: read
+
+jobs:
+ close-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ # https://github.com/marketplace/actions/close-stale-issues
+ - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
+ with:
+ days-before-stale: -1 # Add the stale label manually.
+ days-before-close: 21
+ only-labels: "more info required"
+ stale-issue-label: "more info required"
+ close-issue-message: "Without additional information, we are unfortunately not sure how to resolve this issue. Therefore this issue has been automatically closed. Feel free to comment with additional details and we can re-open this issue."
+ close-pr-message: "Without additional information, we are unfortunately not sure how to address this pull request. Therefore this pull request has been automatically closed. Feel free to comment with additional details or submit a new pull request."
diff --git a/.gitignore b/.gitignore
index 5f02f8d1..1bedca93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,9 @@ gen/
target/
out/
classes/
-gradle.properties
+
+# Kotlin
+.kotlin
# Local build properties
build.properties
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..63a5ea1d
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,238 @@
+# https://docs.gitlab.com/ci/yaml/
+
+# Assumes these environment variables are configured in GitLab CI/CD Settings:
+# - OBX_READ_PACKAGES_TOKEN
+# - SONATYPE_USER
+# - SONATYPE_PWD
+# - GOOGLE_CHAT_WEBHOOK_JAVA_CI
+# Additionally, these environment variables used by the objectbox-publish Gradle script:
+# - ORG_GRADLE_PROJECT_signingKeyFile
+# - ORG_GRADLE_PROJECT_signingKeyId
+# - ORG_GRADLE_PROJECT_signingPassword
+
+variables:
+ OBX_PUBLISH_RELEASE:
+ value: "false"
+ options: [ "false", "true" ]
+ description: "If enabled, builds and depends on release versions and triggers publishing a
+ release of the Java library artifacts to the internal GitLab repository and Maven Central.
+ Don't run multiple times to avoid duplicate artifacts in the GitLab repository.
+ Consult the release checklist before turning this on."
+ # If enabled, builds and depends on release versions. Doesn't publish a release.
+ # See the root Gradle build script for details.
+ OBX_RELEASE: "false"
+
+ # Disable the Gradle daemon. Gradle may run in a Docker container with a shared
+ # Docker volume containing GRADLE_USER_HOME. If the container is stopped after a job
+ # Gradle daemons may get killed, preventing proper clean-up of lock files in GRADLE_USER_HOME.
+ # Use low priority processes to avoid Gradle builds consuming all build machine resources.
+ GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.priority=low"
+ GITLAB_REPO_ARGS: "-PgitlabUrl=$CI_SERVER_URL -PgitlabPrivateTokenName=Deploy-Token -PgitlabPrivateToken=$OBX_READ_PACKAGES_TOKEN"
+ GITLAB_PUBLISH_ARGS: "-PgitlabPublishTokenName=Job-Token -PgitlabPublishToken=$CI_JOB_TOKEN"
+ CENTRAL_PUBLISH_ARGS: "-PsonatypeUsername=$SONATYPE_USER -PsonatypePassword=$SONATYPE_PWD"
+ # CI_COMMIT_REF_SLUG is the branch or tag name, but web-safe (only 0-9, a-z)
+ VERSION_ARGS: "-PversionSuffix=$CI_COMMIT_REF_SLUG"
+
+# Using multiple test stages to avoid running some things in parallel (see job notes).
+stages:
+ - test
+ - publish-maven-internal
+ - publish-maven-central
+ - package-api-docs
+ - triggers
+
+workflow:
+ rules:
+ # Disable merge request pipelines https://docs.gitlab.com/ci/jobs/job_rules/#ci_pipeline_source-predefined-variable
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ when: never
+ # On main branch, always use release versions as old snapshots of dependencies are deleted
+ - if: $CI_COMMIT_REF_NAME == "main"
+ variables:
+ OBX_RELEASE: "true"
+ # To publish a release must use release versions
+ - if: $OBX_PUBLISH_RELEASE == "true"
+ variables:
+ OBX_RELEASE: "true"
+ # In all other cases, create a pipeline
+ - when: always
+
+.docker_linux_x64_android: &docker_linux_x64_android
+ tags:
+ - docker
+ - linux
+ - x64
+ image: objectboxio/buildenv-android:2026-05-12 # With JDK 21, Android build-tools;35.0.0 and platforms;android-35 pre-installed
+ variables:
+ # Image defaults to POSIX (ASCII), set a compatible locale so UTF-8 tests that interact with the file system work.
+ # Check with 'locale -a' for available locales.
+ LC_ALL: "C.UTF-8"
+
+.test_base:
+ before_script:
+ # Print Gradle and JVM version info
+ - ./gradlew -version
+ # Remove any previous JVM (Hotspot) crash log.
+ # "|| true" for an OK exit code if no file is found
+ - rm **/hs_err_pid*.log || true
+ # By default, don't compile and test the Android APIs (only do in test job), there is currently no benefit to test
+ # them using multiple platforms or JDKs.
+ script: ./gradlew $GITLAB_REPO_ARGS $VERSION_ARGS clean build -PexcludeAndroid=true
+ artifacts:
+ when: always
+ paths:
+ - "**/hs_err_pid*.log" # Only on JVM (Hotspot) crash
+ reports:
+ junit: "**/build/test-results/**/TEST-*.xml"
+
+test:
+ <<: *docker_linux_x64_android
+ extends: .test_base
+ stage: test
+ # Overwrite script key to run through ASAN script, compile and test Android APIs and test build of Web API docs.
+ # Note: Address sanitizer is only available on Linux runners (see script).
+ script:
+ # build to assemble, run tests and spotbugs
+ # javadocForWeb to catch API docs errors before releasing
+ # Temporarily disable testing with Address Sanitizer until buildenv images are modernized, see #273
+ # - ./scripts/test-with-asan.sh $GITLAB_REPO_ARGS $VERSION_ARGS clean build javadocForWeb
+ - ./gradlew $GITLAB_REPO_ARGS $VERSION_ARGS clean build javadocForWeb
+ # Overwrite artifacts key to archive spotbugs reports
+ artifacts:
+ when: always
+ paths:
+ - "**/hs_err_pid*.log" # Only on JVM (Hotspot) crash.
+ - "**/build/reports/spotbugs/*.html"
+ reports:
+ junit: "**/build/test-results/**/TEST-*.xml"
+
+.test_jdks:
+ <<: *docker_linux_x64_android
+ extends: .test_base
+ # Overwrite script key to run through ASAN script and to run test task directly to avoid
+ # running SpotBugs (build runs check runs SpotBugs tasks).
+ # Note: Address sanitizer is only available on Linux runners (see script).
+ script:
+ # Temporarily disable testing with Address Sanitizer until buildenv images are modernized, see #273
+ # - ./scripts/test-with-asan.sh $GITLAB_REPO_ARGS $VERSION_ARGS clean :tests:objectbox-java-test:test
+ - ./gradlew $GITLAB_REPO_ARGS $VERSION_ARGS clean :tests:objectbox-java-test:test
+
+# Test oldest supported and a recent JDK.
+test-jdk-8:
+ extends: .test_jdks
+ needs: ["test"]
+ variables:
+ TEST_JDK: 8
+
+# JDK 21 is the default of the current build image, so test the latest LTS JDK 25
+test-jdk-25:
+ extends: .test_jdks
+ needs: ["test"]
+ variables:
+ TEST_JDK: 25
+
+test-macos:
+ extends: .test_base
+ tags:
+ - jdk
+ - mac
+
+test-windows:
+ extends: .test_base
+ tags:
+ - jdk
+ - windows
+ - x64
+
+test-windows-jdk-x86:
+ extends: .test_base
+ tags:
+ - jdk-x86
+ - windows
+ variables:
+ # TEST_WITH_JAVA_X86 makes objectbox-java-test use 32-bit java executable and therefore
+ # 32-bit ObjectBox to run tests (see build.gradle file).
+ # Note: assumes JAVA_HOME_X86 is set to 32-bit JDK path.
+ TEST_WITH_JAVA_X86: "true"
+
+# Publish Maven artifacts to internal Maven repo
+publish-maven-internal:
+ <<: *docker_linux_x64_android
+ stage: publish-maven-internal
+ rules:
+ # Not for tag pipelines (Maven artifact snapshots are versioned using the branch name)
+ - if: $CI_COMMIT_TAG
+ when: never
+ # Not when using release versions and not publishing a release to avoid duplicate artifacts
+ # (GitLab would allow to upload duplicates for a release version)
+ - if: $OBX_RELEASE == "true" && $OBX_PUBLISH_RELEASE != "true"
+ when: never
+ # Not if triggered by upstream project to save on disk space
+ - if: $CI_PIPELINE_SOURCE == "pipeline"
+ when: never
+ # Not for scheduled pipelines to save on disk space
+ - if: $CI_PIPELINE_SOURCE == "schedule"
+ when: never
+ # Otherwise, only if no previous stages failed
+ - when: on_success
+ script:
+ - ./gradlew $GITLAB_REPO_ARGS $GITLAB_PUBLISH_ARGS $VERSION_ARGS publishAllPublicationsToGitLabRepository
+
+# Publish Maven artifacts to public Maven Central repo
+publish-maven-central:
+ <<: *docker_linux_x64_android
+ stage: publish-maven-central
+ rules:
+ # Only if publishing a release, only if no previous stages failed
+ - if: $OBX_PUBLISH_RELEASE == "true"
+ when: on_success
+ before_script:
+ - ci/send-to-gchat.sh "$GOOGLE_CHAT_WEBHOOK_JAVA_CI" --thread $CI_COMMIT_SHA "*Releasing Java libraries (job = $CI_JOB_NAME, ref = $CI_COMMIT_REF_NAME, commit = $CI_COMMIT_SHORT_SHA) ..."
+ script:
+ # Note: supply internal repo as tests use native dependencies that might not be available publicly, yet.
+ - ./gradlew $GITLAB_REPO_ARGS $CENTRAL_PUBLISH_ARGS publishAllPublicationsToSonatypeRepository closeAndReleaseSonatypeStagingRepository
+ after_script:
+ # Also runs on failure, so print CI_JOB_STATUS
+ - ci/send-to-gchat.sh "$GOOGLE_CHAT_WEBHOOK_JAVA_CI" --thread $CI_COMMIT_SHA "*Releasing Java libraries (job = $CI_JOB_NAME, ref = $CI_COMMIT_REF_NAME, commit = $CI_COMMIT_SHORT_SHA) job status = *$CI_JOB_STATUS*"
+ - ci/send-to-gchat.sh "$GOOGLE_CHAT_WEBHOOK_JAVA_CI" --thread $CI_COMMIT_SHA "Check https://repo1.maven.org/maven2/io/objectbox/ in a few minutes."
+
+# Create Java API docs archive
+package-api-docs:
+ <<: *docker_linux_x64_android
+ stage: package-api-docs
+ rules:
+ # Only if publishing a release, only if no previous stages failed
+ - if: $OBX_PUBLISH_RELEASE == "true"
+ when: on_success
+ script:
+ - ./gradlew $GITLAB_REPO_ARGS :objectbox-java:packageJavadocForWeb
+ after_script:
+ - ci/send-to-gchat.sh "$GOOGLE_CHAT_WEBHOOK_JAVA_CI" --thread $CI_COMMIT_SHA "API docs for web available as job artifact $CI_JOB_URL"
+ artifacts:
+ paths:
+ - "objectbox-java/build/dist/objectbox-java-web-api-docs.zip"
+
+# Trigger Gradle plugin build to test new Maven snapshots of this project
+trigger-plugin:
+ stage: triggers
+ rules:
+ # Not for tag pipelines
+ - if: $CI_COMMIT_TAG
+ - when: never
+ # Not when publishing a release
+ - if: $OBX_PUBLISH_RELEASE == "true"
+ when: never
+ # Do not trigger publishing of plugin
+ - if: $CI_COMMIT_BRANCH == "publish"
+ when: never
+ # Not for scheduled pipelines where Maven snapshots of this project do not change
+ - if: $CI_PIPELINE_SOURCE == "schedule"
+ when: never
+ # Otherwise, only if no previous stages failed. Also set allow_failure in case branch does not exist downstream.
+ - when: on_success
+ inherit:
+ variables: false
+ allow_failure: true # Branch might not exist in plugin project
+ trigger:
+ project: objectbox/objectbox-plugin
+ branch: $CI_COMMIT_BRANCH
diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md
new file mode 100644
index 00000000..100c5fd0
--- /dev/null
+++ b/.gitlab/merge_request_templates/Default.md
@@ -0,0 +1,28 @@
+## What does this merge request do?
+
+TODO Link associated issue from title, like: `
#NUMBER`
+
+TODO Briefly list what this merge request is about
+
+## Author's checklist
+
+- [ ] This merge request fully addresses the requirements of the associated task
+- [ ] I did a self-review of the changes and did not spot any issues, among others:
+ - I added unit tests for new or changed behavior; existing and new tests pass
+ - I checked the SpotBugs report for new issues
+ - My code conforms to our coding standards and guidelines
+ - My changes are prepared (focused commits, good messages) so reviewing them is easy for the reviewer
+- [ ] I amended the [changelog](/CHANGELOG.md) if this affects users in any way
+- [ ] I assigned a reviewer to request review
+
+## Reviewer's checklist
+
+- [ ] I reviewed all changes line-by-line and addressed relevant issues. However:
+ - for quickly resolved issues, I considered creating a fixup commit and discussing that, and
+ - instead of many or long comments, I considered a meeting with or a draft commit for the author.
+- [ ] The requirements of the associated task are fully met
+- [ ] I can confirm that:
+ - CI passes
+ - If applicable, coverage percentages do not decrease
+ - New code conforms to standards and guidelines
+ - If applicable, additional checks were done for special code changes (e.g. core performance, binary size, OSS licenses)
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..ece02fa5
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,42 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+- Multi-module Gradle build. Core modules live at the repo root: `objectbox-java-api`, `objectbox-java`, `objectbox-kotlin`, `objectbox-rxjava`, `objectbox-rxjava3`, and `objectbox-android`.
+- `objectbox-android` requires the Android SDK; exclude it with `-PexcludeAndroid=true` if the SDK is unavailable.
+- Test modules live under `tests/`, notably `tests/objectbox-java-test` and `tests/test-proguard`.
+- Shared build logic (e.g., publishing conventions) lives in the `build-logic/` included build.
+- Source follows standard Gradle layout per module (e.g., `src/main/java`, `src/main/kotlin`, `src/test/java`).
+- CI config: `.gitlab-ci.yml` (primary; Jenkins is obsolete); helper scripts live in `scripts/`; build outputs go to `build/`.
+
+## Build, Test, and Development Commands
+- `./gradlew clean build` — full build across modules.
+- `./gradlew :objectbox-java:build` — build a single module.
+- `./gradlew :tests:objectbox-java-test:test` — run the main test suite.
+- `./gradlew :tests:objectbox-java-test:test --tests io.objectbox.query.QueryTest` — run a single test class.
+- `./gradlew -version` — verify Gradle/JDK setup.
+- `./scripts/test-with-asan.sh --stacktrace clean build` — CI-style run with ASAN (requires native deps; currently disabled in CI, see issue #273).
+
+## Coding Style & Naming Conventions
+- No repo-wide formatter config is present; follow existing Java/Kotlin style in the touched files and keep changes minimal.
+- Use 4-space indentation and standard Java/Kotlin naming conventions; prefer package naming under `io.objectbox`.
+- New tests in `tests/objectbox-java-test` should name methods as `{attribute}_{queryCondition}_{expectation}` (see `tests/README.md`).
+
+## Testing Guidelines
+- Tests are JUnit-based (see `tests/objectbox-java-test/build.gradle.kts`).
+- Tests run against the real native database library (`io.objectbox:objectbox-linux`/`-macos`/`-windows` from Maven).
+ Snapshot versions of these resolve only via the internal GitLab repository (`gitlabUrl`/`gitlabPrivateToken` Gradle properties, see `settings.gradle.kts`).
+- Place new tests under `tests/objectbox-java-test/src/test/java` (or Kotlin equivalents).
+- Legacy tests may still use the `test*` prefix; new tests should follow the naming convention above.
+
+## Key Classes & Hotspots
+- Core API: `io.objectbox.BoxStore`, `io.objectbox.Box`, and `io.objectbox.Transaction` (see `tests/objectbox-java-test/src/test/java/io/objectbox/BoxStoreTest.java` and `tests/objectbox-java-test/src/test/java/io/objectbox/BoxTest.java`).
+- Queries: `io.objectbox.query.Query`, `QueryBuilder`, and `QueryCondition` (see `tests/objectbox-java-test/src/test/java/io/objectbox/query/QueryTest.java` and `tests/objectbox-java-test/src/test/java/io/objectbox/query/QueryTest2.java`).
+- Relations: `io.objectbox.relation.ToOne` and `ToMany` (see `tests/objectbox-java-test/src/test/java/io/objectbox/relation/RelationTest.java`).
+- Sync: `io.objectbox.sync.SyncClient`, `SyncServer`, and `SyncCredentials` (see `tests/objectbox-java-test/src/test/java/io/objectbox/sync/SyncTest.java`).
+
+## Commit & Pull Request Guidelines
+- The default branch for development and PRs is `dev`; the `publish` branch is used for releases to Maven Central.
+- Recent history uses a short area prefix, e.g., `QueryTest: use new API...` or `TargetIdProperty: improve...`. Follow this pattern for new commits.
+- Add user-facing changes to the "Next release" section of `CHANGELOG.md`.
+- PRs should include a concise summary, rationale, and the exact test commands run.
+- If changes affect native or CI behavior, call that out explicitly (CI tests against the oldest supported JDK 8 and a recent JDK, see `.gitlab-ci.yml`).
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..3d89fa82
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,318 @@
+# Changelog
+
+Notable changes to the ObjectBox Java library.
+
+For more insights into what changed in the database libraries, [check the ObjectBox C changelog](https://github.com/objectbox/objectbox-c/blob/main/CHANGELOG.md).
+
+## 6.0.0-beta - 2026-07-14
+
+* Update Android and JVM libraries to database version `6.0.0-beta-2026-07-13`
+ * Admin: modernized user interface based on Vue.js 3 and Material Design 3
+ * Admin: optimized pages now loading faster
+ * Android: log messages now use the logcat tag "ObjectBox" (previously "Box")
+ * Internal fixes and improvements
+* objectbox-kotlin: requires at least Kotlin compiler and standard library 2.2.0
+
+### Sync
+
+* **New** [Mesh Sync API](https://sync.objectbox.io/mesh-sync): peer-to-peer (P2P) synchronization between sync clients without a central server.
+ * Attach a `MeshConfig` via `SyncBuilder.mesh()` and query the running mesh via `SyncClient.getMesh()` (state, connected peer count, statistics counters).
+ Mesh configurations are created by ObjectBox platform libraries that provide a mesh network (transport).
+ * New artifact `objectbox-meshsync-android`: provides the mesh network for Android using Google Nearby Connections. Add it next to the Sync variant of the Android library (like `objectbox-sync-android`) and create a mesh configuration with `AndroidMeshSync.createConfig(context, meshId)`. It includes the permissions required by Nearby Connections and the `play-services-nearby` dependency. See [its README for details](objectbox-meshsync-android/README.md).
+
+## 5.4.2 - 2026-05-05
+
+- Update Android and JVM libraries to database version `5.3.2-2026-05-05`
+ - Fixed a corner case with "unique replace on conflict" and absent (e.g. null) sync clock value
+ - Internal fixes and improvements
+
+## 5.4.1 - 2026-03-26
+
+- Update Android and JVM libraries to database version `5.3.1-2026-03-26`
+
+### Sync
+
+- Critical fix for sync clock property handling
+
+## 5.4.0 - 2026-03-24
+
+- Update Android and JVM libraries to database version `5.3.0-2026-03-23`
+- Admin: Status tab "Count and Sizes" and "System and Info" improved
+
+### Sync
+
+- Conflict resolution via the new annotations `@SyncClock` and `@SyncPrecedence` (allows last win with custom conflict
+ resolution)
+- Fix bad Sync client state after multiple full sync messages were interrupted
+- Fix adding indexes to Sync types
+
+## 5.3.0 - 2026-03-10
+
+- Gradle plugin: to apply the plugin, it is no longer necessary to add a manual mapping of the plugin ID in your
+ projects Gradle settings file. This was resolved by publishing Gradle plugin marker artifacts.
+ [#960](https://github.com/objectbox/objectbox-java/issues/960)
+- Gradle plugin: it is no longer necessary to apply the plugin after the dependencies block when manually including
+ ObjectBox dependencies.
+- Update database libraries for Android and JVM to database version `5.2.0-next-2026-03-10`
+- (Minor) performance improvements of HNSW vector search on Linux ARM targets
+
+### Sync
+
+- Sync protocol version 10
+- SyncClient: support updating filter variables. After login, stage updates using put and remove, then schedule to send
+ them to the server with `applyFilterVariables()`.
+- Update client sync filters variables when online
+- Clients report errors to the server
+
+### Migration instructions
+
+#### Drop the Gradle plugin ID mapping
+
+If your `settings.gradle.kts` or `settings.gradle` file contains lines similar to:
+
+```kotlin
+pluginManagement {
+ repositories {
+ mavenCentral()
+ }
+
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "io.objectbox") {
+ useModule("io.objectbox:objectbox-gradle-plugin:${requested.version}")
+ }
+ }
+ }
+}
+```
+
+Update them to remove the plugin mapping configuration:
+
+```kotlin
+pluginManagement {
+ repositories {
+ mavenCentral()
+ }
+}
+```
+
+#### Apply the plugin using plugins syntax
+
+If your build scripts are applying the plugin after the dependencies block similar to:
+
+```kotlin
+dependencies {
+ // ...
+}
+
+apply(plugin = "io.objectbox")
+```
+
+Change them to apply the plugin using plugins syntax:
+
+```kotlin
+plugins {
+ id("io.objectbox")
+}
+
+dependencies {
+ // ...
+}
+```
+
+## 5.2.0 - 2026-02-16
+
+- The [ObjectBox Gradle plugin](https://github.com/objectbox/objectbox-java-generator) requires JDK 11 and Android
+ Gradle Plugin 8.1 or newer.
+- Update database libraries for Android and JVM to database version `5.1.1-pre-2026-02-16`.
+- Admin: new "Counts and Sizes" view (via Status page) to give an overview over all types their object counts and sizes
+- Admin: fix reload via browser to stay on the page
+- Admin: schema view to display flags as text
+- Admin: refresh button for the data view
+
+### Sync
+
+- Add simplified `Sync.client(boxStore)` helper method. Move URL and credentials options to builder, add variants that
+ accept multiple URLs and credentials. Deprecate the existing helper methods.
+- Add Sync client builder option to configure Sync behavior using
+ [SyncFlags](objectbox-java/src/main/java/io/objectbox/sync/SyncFlags.java).
+
+## 5.1.0 - 2026-01-26
+
+- Add [ObjectBoxThreadPoolExecutor](objectbox-java/src/main/java/io/objectbox/ObjectBoxThreadPoolExecutor.java), a
+ default implementation of a `ThreadPoolExecutor` that properly cleans up thread-local Box resources.
+- Add methods `newCachedThreadPoolExecutor()` and `newFixedThreadPoolExecutor()` to `BoxStore` to help create instances
+ of `ObjectBoxThreadPoolExecutor` for common uses.
+- Add methods `newCachedThreadPoolDispatcher()` and `newFixedThreadPoolDispatcher()` to the Kotlin extension functions
+ for `BoxStore` to help create common coroutine dispatchers backed by an `ObjectBoxThreadPoolExecutor`.
+- `BoxStore.runInTx` and `callInTx` close a write cursor even if the runnable or callable throws. This would previously
+ result in cursor not closed warnings when the cursor was closed by the finalizer daemon.
+- `BoxStore` no longer gets stuck while closing when deleting transactions created in other threads with open relation
+ cursors.
+- Admin: the Schema view now shows if external name of types and properties if configured.
+- Admin: the Schema view now shows the type as text (e.g. "String") instead of the internal type ID.
+
+### Sync
+
+- New Sync protocol V8: using new clients also requires a server update
+- Remove superfluous sync listener triggers when sync filters "report updates" (SKIP_TX)
+- Sync clients compress earlier: reduces disk storage for outgoing data
+- Reworked certificates for Apple platforms
+- Removed support for older Sync protocol versions before 2024-09; protocol V5+ is now required.
+
+## 5.0.1 - 2025-09-30
+
+- Update runtime libraries for Android and JVM to database version `5.0.0-2025-09-27`.
+- Fix a race condition with a closing store and still active transactions that kept the store from closing.
+ For Android this may fix some rare ANR issues.
+
+### Sync
+
+- Add `filterVariables` option to `SyncClient` builder for [Sync filters](https://sync.objectbox.io/sync-server/sync-filters)
+ introduced in version 5.0.
+
+## 5.0.0 - 2025-09-17
+
+- Includes runtime libraries for Android and JVM with database version `5.0.0-2025-09-16`.
+ - Android: Prior to Android 8.0, don't crash when inserting objects with string lists whose size exceeds the local
+ reference table size. [#1215](https://github.com/objectbox/objectbox-java/issues/1215)
+ - ToOne relations: when deleting an object with an ID larger than the maximum 32-bit unsigned integer
+ (`4_294_967_295`) that is used as the target object of a ToOne, correctly re-set the target ID of the ToOne to
+ `0`. [objectbox-dart#740](https://github.com/objectbox/objectbox-dart/issues/740)
+- When re-creating a `BoxStore` for the same directory and `close()` wasn't called on the previous instance, don't throw
+ an "Another BoxStore is still open for this directory" exception. Note that calling `close()` *is recommended* before
+ creating a new instance. [#1201](https://github.com/objectbox/objectbox-java/issues/1201)
+- When using `BoxStoreBuilder.buildDefault()`, don't leak the Store when setting it as default fails.
+- To help diagnose, print stacks of all threads in the internal thread pool if shutting it down takes too long when
+ closing `BoxStore`.
+- Remove deprecated APIs:
+ - `Query.setParameters` methods that set a single parameter, use the `setParameter` methods instead.
+ - `Box.removeByKeys`, use `Box.removeByIds` instead.
+ - `BoxStore.sizeOnDisk`, use `getDbSize` or `getDbSizeOnDisk` instead which properly handle in-memory databases.
+ - `BoxStoreBuilder.debugTransactions`, use `debugFlags(DebugFlags.LOG_TRANSACTIONS_READ | DebugFlags.LOG_TRANSACTIONS_WRITE)` instead.
+ - `SyncServerBuilder` `peer` configuration options, use the `clusterPeer` options instead.
+ - `io.objectbox.DebugFlags`, use `io.objectbox.config.DebugFlags` instead.
+ - `ValidateOnOpenMode` constants, use `ValidateOnOpenModePages` instead.
+ - DAOcompat compatibility query methods. Use the regular query API instead.
+
+### Sync
+
+- Support Sync server version 5.0.
+ - **User-Specific Data Sync**: support configuring [Sync filter](https://sync.objectbox.io/sync-server/sync-filters)
+ variables on `SyncClient`.
+
+## 4.3.1 - 2025-08-12
+
+- Requires at least Kotlin compiler and standard library 1.7.
+- Data Observers: closing a Query now waits on a running publisher to finish its query, preventing a VM crash. [#1147](https://github.com/objectbox/objectbox-java/issues/1147)
+- Update database libraries for Android and JVM to version `4.3.1` (include database version `4.3.1-2025-08-02`).
+
+## 4.3.0 - 2025-05-13
+
+- Basic support for boolean array properties (`boolean[]` in Java or `BooleanArray` in Kotlin).
+- The Windows database library now statically links the MSVC runtime to avoid crashes in incompatible `msvcp140.dll`
+ shipped with some JDKs.
+- External property types (via [MongoDB connector](https://sync.objectbox.io/mongodb-sync-connector)):
+ - add `JSON_TO_NATIVE` to support sub (embedded/nested) documents/arrays in MongoDB
+ - support ID mapping to UUIDs (v4 and v7)
+- Admin: add class and dependency diagrams to the schema page (view and download).
+- Admin: improved data view for large vectors by displaying only the first elements and the full vector in a dialog.
+- Admin: detects images stored as bytes and shows them as such (PNG, GIF, JPEG, SVG, WEBP).
+
+### Sync
+
+- Add "Log Events" for important server events, which can be viewed on a new Admin page.
+- Detect and ignore changes for objects that were put but were unchanged.
+- The limit for message size was raised to 32 MB.
+- Transactions above the message size limit now already fail on the client (to better enforce the limit).
+
+## 4.2.0 - 2025-03-04
+
+- Add new query conditions `equalKeyValue`, `greaterKeyValue`, `lessKeyValue`, `lessOrEqualKeyValue`, and
+ `greaterOrEqualKeyValue` that are helpful to write complex queries for [string maps](https://docs.objectbox.io/advanced/custom-types#flex-properties).
+ These methods support `String`, `long` and `double` data types for the values in the string map.
+- Deprecate the `containsKeyValue` condition, use the new `equalKeyValue` condition instead.
+- Android: to build, at least Android Plugin 8.1.1 and Gradle 8.2.1 are required.
+
+## 4.1.0 - 2025-01-30
+
+- Vector Search: add new `VectorDistanceType.GEO` distance type to perform vector searches on geographical coordinates.
+ This is particularly useful for location-based applications.
+- Android: require Android 5.0 (API level 21) or higher.
+- Note on Windows JVM: We've seen crashes on Windows when creating a BoxStore on some JVM versions.
+ If this should happen to you, make sure to update your JVM to the latest patch release
+ (8.0.432+6, 11.0.25+9, 17.0.13+11 and 21.0.5+11-LTS are known to work).
+
+### Sync
+
+- Add JWT authentication
+- Sync clients can now send multiple credentials for login
+
+## 4.0.3 - 2024-10-15
+
+- Make closing the Store more robust. In addition to transactions, it also waits for ongoing queries. This is just an
+ additional safety net. Your apps should still make sure to finish all Store operations, like queries, before closing it.
+- [Flex properties](https://docs.objectbox.io/advanced/custom-types#flex-properties) support `null` map and list values.
+- Some minor vector search performance improvements.
+
+### Sync
+
+- **Fix a serious regression, please update as soon as possible.**
+- Add new options, notably for cluster configuration, when building `SyncServer`. Improve documentation.
+ Deprecate the old peer options in favor of the new cluster options.
+- Add `SyncHybrid`, a combination of a Sync client and a Sync server. It can be used in local cluster setups, in
+ which a "hybrid" functions as a client & cluster peer (server).
+
+## 4.0.2 - 2024-08-20
+
+- Add convenience `oneOf` and `notOneOf` conditions that accept `Date` to avoid manual conversion using `getTime()`.
+- When `BoxStore` is closing, briefly wait on active transactions to finish.
+- Guard against crashes when `BoxStore` was closed, but database operations do still occur concurrently (transactions are still active).
+
+## 4.0.1 - 2024-06-03
+
+- Examples: added [Vector Search example](https://github.com/objectbox/objectbox-examples/tree/main/java-main-vector-search) that demonstrates how to perform on-device [approximate nearest neighbor (ANN) search](https://docs.objectbox.io/on-device-vector-search).
+- Revert deprecation of `Box.query()`, it is still useful for queries without any condition.
+- Add note on old query API methods of `QueryBuilder` that they are not recommended for new projects. Use [the new query APIs](https://docs.objectbox.io/queries) instead.
+- Update and expand documentation on `ToOne` and `ToMany`.
+
+## 4.0.0 - Vector Search - 2024-05-16
+
+**ObjectBox now supports** [**Vector Search**](https://docs.objectbox.io/ann-vector-search) to enable efficient similarity searches.
+
+This is particularly useful for AI/ML/RAG applications, e.g. image, audio, or text similarity. Other use cases include semantic search or recommendation engines.
+
+Create a Vector (HNSW) index for a floating point vector property. For example, a `City` with a location vector:
+
+```java
+@Entity
+public class City {
+
+ @HnswIndex(dimensions = 2)
+ float[] location;
+
+}
+```
+
+Perform a nearest neighbor search using the new `nearestNeighbors(queryVector, maxResultCount)` query condition and the new "find with scores" query methods (the score is the distance to the query vector). For example, find the 2 closest cities:
+
+```java
+final float[] madrid = {40.416775F, -3.703790F};
+final Query query = box
+ .query(City_.location.nearestNeighbors(madrid, 2))
+ .build();
+final City closest = query.findWithScores().get(0).get();
+```
+
+For an introduction to Vector Search, more details and other supported languages see the [Vector Search documentation](https://docs.objectbox.io/ann-vector-search).
+
+- BoxStore: deprecated `BoxStore.sizeOnDisk()`. Instead use one of the new APIs to determine the size of a database:
+ - `BoxStore.getDbSize()` which for a file-based database returns the file size and for an in-memory database returns the approximately used memory,
+ - `BoxStore.getDbSizeOnDisk()` which only returns a non-zero size for a file-based database.
+- Query: add properly named `setParameter(prop, value)` methods that only accept a single parameter value, deprecated the old `setParameters(prop, value)` variants.
+- Sync: add `SyncCredentials.userAndPassword(user, password)`.
+- Gradle plugin: the license of the [Gradle plugin](https://github.com/objectbox/objectbox-java-generator) has changed to the GNU Affero General Public License (AGPL).
+
+## Previous versions
+
+See the [Changelogs in the documentation](https://docs.objectbox.io/changelogs).
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..5cbfa334
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+See @AGENTS.md for repository guidelines
diff --git a/Jenkinsfile b/Jenkinsfile
index a7b8499b..0b821e72 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,10 +1,21 @@
-// dev branch only: every 30 minutes at night (1:00 - 5:00)
-String cronSchedule = BRANCH_NAME == 'dev' ? '*/30 1-5 * * *' : ''
+// dev branch only: run every hour at 30th minute at night (1:00 - 5:00)
+// Avoid running at the same time as integration tests: uses this projects snapshots
+// so make sure to not run in the middle of uploading a new snapshot to avoid dependency resolution errors.
+String cronSchedule = BRANCH_NAME == 'dev' ? '30 1-5 * * *' : ''
String buildsToKeep = '500'
-String gradleArgs = '-Dorg.gradle.daemon=false --stacktrace'
+String gradleArgs = '--stacktrace'
boolean isPublish = BRANCH_NAME == 'publish'
-String versionPostfix = isPublish ? '' : BRANCH_NAME // Build script detects empty string as not set.
+String versionSuffix = isPublish ? '' : BRANCH_NAME // Build script detects empty string as not set.
+
+// Note: using single quotes to avoid Groovy String interpolation leaking secrets.
+def signingArgs = '-PsigningKeyFile=$SIGNING_FILE -PsigningKeyId=$SIGNING_ID -PsigningPassword=$SIGNING_PWD'
+def gitlabRepoArgs = '-PgitlabUrl=$GITLAB_URL -PgitlabPrivateToken=$GITLAB_TOKEN'
+def uploadRepoArgsCentral = '-PsonatypeUsername=$OSSRH_LOGIN_USR -PsonatypePassword=$OSSRH_LOGIN_PSW'
+
+boolean startedByTimer = currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause').size() > 0
+def buildCauses = currentBuild.getBuildCauses()
+echo "startedByTimer=$startedByTimer, build causes: $buildCauses"
// https://jenkins.io/doc/book/pipeline/syntax/
pipeline {
@@ -12,26 +23,19 @@ pipeline {
environment {
GITLAB_URL = credentials('gitlab_url')
- MVN_REPO_LOGIN = credentials('objectbox_internal_mvn_user')
- MVN_REPO_URL = credentials('objectbox_internal_mvn_repo_http')
- MVN_REPO_ARGS = "-PinternalObjectBoxRepo=$MVN_REPO_URL " +
- "-PinternalObjectBoxRepoUser=$MVN_REPO_LOGIN_USR " +
- "-PinternalObjectBoxRepoPassword=$MVN_REPO_LOGIN_PSW"
- MVN_REPO_UPLOAD_URL = credentials('objectbox_internal_mvn_repo')
- MVN_REPO_UPLOAD_ARGS = "-PpreferredRepo=$MVN_REPO_UPLOAD_URL " +
- "-PpreferredUsername=$MVN_REPO_LOGIN_USR " +
- "-PpreferredPassword=$MVN_REPO_LOGIN_PSW " +
- "-PversionPostFix=$versionPostfix"
+ GITLAB_TOKEN = credentials('GITLAB_TOKEN_ALL')
+ GITLAB_INTEG_TESTS_TRIGGER_URL = credentials('gitlab-trigger-java-integ-tests')
// Note: for key use Jenkins secret file with PGP key as text in ASCII-armored format.
- ORG_GRADLE_PROJECT_signingKeyFile = credentials('objectbox_signing_key')
- ORG_GRADLE_PROJECT_signingKeyId = credentials('objectbox_signing_key_id')
- ORG_GRADLE_PROJECT_signingPassword = credentials('objectbox_signing_key_password')
+ SIGNING_FILE = credentials('objectbox_signing_key')
+ SIGNING_ID = credentials('objectbox_signing_key_id')
+ SIGNING_PWD = credentials('objectbox_signing_key_password')
}
options {
buildDiscarder(logRotator(numToKeepStr: buildsToKeep, artifactNumToKeepStr: buildsToKeep))
timeout(time: 1, unit: 'HOURS') // If build hangs (regular build should be much quicker)
- gitLabConnection("${env.GITLAB_URL}")
+ disableConcurrentBuilds() // limit to 1 job per branch
+ gitLabConnection('objectbox-gitlab-connection')
}
triggers {
@@ -43,8 +47,6 @@ pipeline {
stages {
stage('init') {
steps {
- sh 'chmod +x gradlew'
- sh 'chmod +x ci/test-with-asan.sh'
sh './gradlew -version'
// "|| true" for an OK exit code if no file is found
@@ -54,39 +56,75 @@ pipeline {
stage('build-java') {
steps {
- sh "./ci/test-with-asan.sh $gradleArgs $MVN_REPO_ARGS -Dextensive-tests=true clean test " +
- "--tests io.objectbox.FunctionalTestSuite " +
- "--tests io.objectbox.test.proguard.ObfuscatedEntityTest " +
- "--tests io.objectbox.rx.QueryObserverTest " +
- "--tests io.objectbox.rx3.QueryObserverTest " +
- "spotbugsMain assemble"
+ sh "./scripts/test-with-asan.sh $gradleArgs $signingArgs $gitlabRepoArgs clean build"
+ }
+ post {
+ always {
+ junit '**/build/test-results/**/TEST-*.xml'
+ archiveArtifacts artifacts: 'tests/*/hs_err_pid*.log', allowEmptyArchive: true // Only on JVM crash.
+ recordIssues(tool: spotBugs(pattern: '**/build/reports/spotbugs/*.xml', useRankAsPriority: true))
+ }
+ }
+ }
+
+ // Test oldest supported and a recent JDK.
+ // Note: can not run these in parallel using a matrix configuration as Gradle would step over itself.
+ // Also shouldn't add agent config to avoid that as it triggers a separate job which can easily cause deadlocks.
+ stage("test-jdk-8") {
+ environment {
+ TEST_JDK = "8"
+ }
+ steps {
+ // "|| true" for an OK exit code if no file is found
+ sh 'rm tests/objectbox-java-test/hs_err_pid*.log || true'
+ // Note: do not run check task as it includes SpotBugs.
+ sh "./scripts/test-with-asan.sh $gradleArgs $gitlabRepoArgs clean :tests:objectbox-java-test:test"
+ }
+ post {
+ always {
+ junit '**/build/test-results/**/TEST-*.xml'
+ archiveArtifacts artifacts: 'tests/*/hs_err_pid*.log', allowEmptyArchive: true // Only on JVM crash.
+ }
+ }
+ }
+ stage("test-jdk-16") {
+ environment {
+ TEST_JDK = "16"
+ }
+ steps {
+ // "|| true" for an OK exit code if no file is found
+ sh 'rm tests/objectbox-java-test/hs_err_pid*.log || true'
+ // Note: do not run check task as it includes SpotBugs.
+ sh "./scripts/test-with-asan.sh $gradleArgs $gitlabRepoArgs clean :tests:objectbox-java-test:test"
+ }
+ post {
+ always {
+ junit '**/build/test-results/**/TEST-*.xml'
+ archiveArtifacts artifacts: 'tests/*/hs_err_pid*.log', allowEmptyArchive: true // Only on JVM crash.
+ }
}
}
stage('upload-to-internal') {
steps {
- sh "./gradlew $gradleArgs $MVN_REPO_ARGS $MVN_REPO_UPLOAD_ARGS uploadArchives"
+ sh "./gradlew $gradleArgs $signingArgs $gitlabRepoArgs -PversionSuffix=$versionSuffix publishMavenJavaPublicationToGitLabRepository"
}
}
- stage('upload-to-bintray') {
+ stage('upload-to-central') {
when { expression { return isPublish } }
environment {
- BINTRAY_URL = credentials('bintray_url')
- BINTRAY_LOGIN = credentials('bintray_login')
+ OSSRH_LOGIN = credentials('ossrh-login')
}
steps {
googlechatnotification url: 'id:gchat_java',
- message: "*Publishing* ${currentBuild.fullDisplayName} to Bintray...\n${env.BUILD_URL}"
+ message: "*Publishing* ${currentBuild.fullDisplayName} to Central...\n${env.BUILD_URL}"
- // Note: supply internal Maven repo as tests use native dependencies (can't publish those without the Java libraries).
- // Note: add quotes around URL parameter to avoid line breaks due to semicolon in URL.
- sh "./gradlew $gradleArgs $MVN_REPO_ARGS " +
- "\"-PpreferredRepo=${BINTRAY_URL}\" -PpreferredUsername=${BINTRAY_LOGIN_USR} -PpreferredPassword=${BINTRAY_LOGIN_PSW} " +
- "uploadArchives"
+ // Note: supply internal repo as tests use native dependencies that might not be published, yet.
+ sh "./gradlew $gradleArgs $signingArgs $gitlabRepoArgs $uploadRepoArgsCentral publishMavenJavaPublicationToSonatypeRepository closeAndReleaseStagingRepository"
googlechatnotification url: 'id:gchat_java',
- message: "Published ${currentBuild.fullDisplayName} successfully to Bintray - check https://bintray.com/objectbox/objectbox\n${env.BUILD_URL}"
+ message: "Published ${currentBuild.fullDisplayName} successfully to Central - check https://repo1.maven.org/maven2/io/objectbox/ in a few minutes.\n${env.BUILD_URL}"
}
}
@@ -95,10 +133,6 @@ pipeline {
// For global vars see /jenkins/pipeline-syntax/globals
post {
always {
- junit '**/build/test-results/**/TEST-*.xml'
- archiveArtifacts artifacts: 'tests/*/hs_err_pid*.log', allowEmptyArchive: true // Only on JVM crash.
- recordIssues(tool: spotBugs(pattern: '**/build/reports/spotbugs/*.xml', useRankAsPriority: true))
-
googlechatnotification url: 'id:gchat_java', message: "${currentBuild.currentResult}: ${currentBuild.fullDisplayName}\n${env.BUILD_URL}",
notifyFailure: 'true', notifyUnstable: 'true', notifyBackToNormal: 'true'
}
@@ -123,6 +157,17 @@ pipeline {
success {
updateGitlabCommitStatus name: 'build', state: 'success'
+ script {
+ if (startedByTimer) {
+ echo "Started by timer, not triggering integration tests"
+ } else {
+ // Trigger integration tests in GitLab
+ // URL configured like /api/v4/projects//trigger/pipeline?token=
+ // Note: do not fail on error in case ref does not exist, only output response
+ // --silent --show-error disable progress output but still show errors
+ sh 'curl --silent --show-error -X POST "$GITLAB_INTEG_TESTS_TRIGGER_URL&ref=$GIT_BRANCH"'
+ }
+ }
}
}
}
diff --git a/README.md b/README.md
index 0493b6e3..761ae1ad 100644
--- a/README.md
+++ b/README.md
@@ -1,107 +1,455 @@
-
+
-# Do you ♥️ using ObjectBox?
-[](https://twitter.com/ObjectBox_io)
+
-We want to [hear about your app](https://docs.google.com/forms/d/e/1FAIpQLScIYiOIThcq-AnDVoCvnZOMgxO4S-fBtDSFPQfWldJnhi2c7Q/viewform)!
-It will - literally - take just a minute, but help us a lot. Thank you! 🙏
+
+ * Usage requires manually configuring some ObjectBox dependencies, see the
+ * documentation for more details.
+ *
+ *
+ * if (BuildConfig.DEBUG) {
+ * boolean started = new Admin(boxStore).start(this);
+ * Log.i("ObjectBoxAdmin", "Started: " + started);
+ * }
+ *
+ * After {@link #start} is called a notification is displayed. Tap it to open this Admin URL on the device.
+ * Alternatively, look for a logcat message from Admin to obtain the URL. Use {@code adb forward} to access
+ * the URL on your development machine.
+ *
+ * Tapping the notification starts a foreground service to keep this app running in the background.
+ * Stop this keep-alive service from the notification.
+ *
+ * See the web documentation for details.
+ */
+public class Admin {
+
+ private int notificationId;
+
+ /**
+ * Creates a helper to control Admin for the given {@code boxStore}.
+ *
+ * See the {@link Admin class documentation} for details.
+ */
+ public Admin(BoxStore boxStore) {
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ */
+ public int getNotificationId() {
+ return notificationId;
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ */
+ public void setNotificationId(int notificationId) {
+ this.notificationId = notificationId;
+ }
+
+ /**
+ * Starts the ObjectBox Admin HTTP server.
+ *
+ * See the {@link Admin class documentation} for details.
+ */
+ public boolean start(Context context) {
+ return false;
+ }
+
+}
diff --git a/objectbox-android/src/adminExcluded/java/io/objectbox/android/AndroidObjectBrowser.java b/objectbox-android/src/adminExcluded/java/io/objectbox/android/AndroidObjectBrowser.java
new file mode 100644
index 00000000..f5651a56
--- /dev/null
+++ b/objectbox-android/src/adminExcluded/java/io/objectbox/android/AndroidObjectBrowser.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 ObjectBox Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.objectbox.android;
+
+import android.content.Context;
+
+import io.objectbox.BoxStore;
+
+/**
+ * A helper class to start the ObjectBox Admin web app used to browse and gain insights into the database.
+ *
+ * Usage requires manually configuring some ObjectBox dependencies, see the
+ * documentation for more details.
+ *
+ *
+ * if (BuildConfig.DEBUG) {
+ * boolean started = new AndroidObjectBrowser(boxStore).start(this);
+ * Log.i("ObjectBoxAdmin", "Started: " + started);
+ * }
+ *
+ * After {@link #start} is called a notification is displayed. Tap it to open this Admin URL on the device.
+ * Alternatively, look for a logcat message from Admin to obtain the URL. Use {@code adb forward} to access
+ * the URL on your development machine.
+ *
+ * Tapping the notification starts a foreground service to keep this app running in the background.
+ * Stop this keep-alive service from the notification.
+ *
+ * See the web documentation for details.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+@Deprecated
+public class AndroidObjectBrowser extends Admin {
+
+ /**
+ * Creates a helper to control Admin for the given {@code boxStore}.
+ *
+ * See the {@link AndroidObjectBrowser class documentation} for details.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public AndroidObjectBrowser(BoxStore boxStore) {
+ super(boxStore);
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public int getNotificationId() {
+ return super.getNotificationId();
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public void setNotificationId(int notificationId) {
+ super.setNotificationId(notificationId);
+ }
+
+ /**
+ * Starts the ObjectBox Admin HTTP server.
+ *
+ * See the {@link AndroidObjectBrowser class documentation} for details.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public boolean start(Context context) {
+ return super.start(context);
+ }
+}
diff --git a/objectbox-android/src/adminIncluded/AndroidManifest.xml b/objectbox-android/src/adminIncluded/AndroidManifest.xml
new file mode 100644
index 00000000..508901c5
--- /dev/null
+++ b/objectbox-android/src/adminIncluded/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/objectbox-android/src/adminIncluded/java/io/objectbox/android/Admin.java b/objectbox-android/src/adminIncluded/java/io/objectbox/android/Admin.java
new file mode 100644
index 00000000..5bad3ddb
--- /dev/null
+++ b/objectbox-android/src/adminIncluded/java/io/objectbox/android/Admin.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2022 ObjectBox Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.objectbox.android;
+
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import io.objectbox.BoxStore;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+/**
+ * A helper class to start the ObjectBox Admin web app used to browse and gain insights into the database.
+ *
+ * Usage requires manually configuring some ObjectBox dependencies, see the
+ * documentation for more details.
+ *
+ *
+ * if (BuildConfig.DEBUG) {
+ * boolean started = new Admin(boxStore).start(this);
+ * Log.i("ObjectBoxAdmin", "Started: " + started);
+ * }
+ *
+ * After {@link #start} is called a notification is displayed. Tap it to open this Admin URL on the device.
+ * Alternatively, look for a logcat message from Admin to obtain the URL. Use {@code adb forward} to access
+ * the URL on your development machine.
+ *
+ * Tapping the notification starts a foreground service to keep this app running in the background.
+ * Stop this keep-alive service from the notification.
+ *
+ * See the web documentation for details.
+ */
+public class Admin {
+
+ private static final String TAG = "ObjectBoxAdmin";
+ private static final String NOTIFICATION_CHANNEL_ID = "objectbox-browser";
+
+ private final BoxStore boxStore;
+ private int notificationId;
+
+ /**
+ * Creates a helper to control Admin for the given {@code boxStore}.
+ *
+ * See the {@link Admin class documentation} for details.
+ */
+ public Admin(BoxStore boxStore) {
+ this.boxStore = boxStore;
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ */
+ public int getNotificationId() {
+ return notificationId;
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ */
+ public void setNotificationId(int notificationId) {
+ this.notificationId = notificationId;
+ }
+
+ /**
+ * Starts the ObjectBox Admin HTTP server.
+ *
+ * See the {@link Admin class documentation} for details.
+ */
+ public boolean start(Context context) {
+ if (!BoxStore.isObjectBrowserAvailable()) {
+ return false;
+ }
+
+ // compare with objectbox-android-objectbrowser/src/main/AndroidManifest.xml
+ // SecurityException if no INTERNET permission
+ context.enforcePermission(Manifest.permission.INTERNET, Process.myPid(), Process.myUid(), null);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
+ && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ // SecurityException if no FOREGROUND_SERVICE permission
+ context.enforcePermission(Manifest.permission.FOREGROUND_SERVICE, Process.myPid(), Process.myUid(), null);
+ }
+
+ int alreadyRunningPort = boxStore.getObjectBrowserPort();
+ if (alreadyRunningPort != 0) {
+ Log.w(TAG, "ObjectBox Admin is already running at port " + alreadyRunningPort);
+ return false;
+ }
+ String url = boxStore.startObjectBrowser();
+ if (url == null) {
+ return false;
+ }
+ Log.i(TAG, "ObjectBox Admin running at URL: " + url);
+ int port = boxStore.getObjectBrowserPort();
+ Log.i(TAG, "To access the ObjectBox Admin URL on your machine run: adb forward tcp:" + port + " tcp:" + port);
+
+
+ if (notificationId == 0) {
+ notificationId = 19770000 + port;
+ }
+
+ // build intent and show notification
+ Intent intent = new Intent(context, AdminNotificationReceiver.class);
+ intent.setAction(AdminNotificationReceiver.ACTION_KEEP_ALIVE);
+ intent.putExtra(AdminKeepAliveService.EXTRA_KEY_URL, url);
+ intent.putExtra(AdminKeepAliveService.EXTRA_KEY_PORT, port);
+ intent.putExtra(AdminKeepAliveService.EXTRA_KEY_NOTIFICATION_ID, notificationId);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
+ buildPendingIntentFlags(PendingIntent.FLAG_CANCEL_CURRENT));
+
+ NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ // Note: may be null in case where notification service is not available on device (e.g. custom Android OS).
+ if (manager != null) {
+ // On Android 13 or newer notifications for apps are turned off by default,
+ // developers need to turn on notifications for the app through system settings
+ // or request the permission from the user.
+ // https://developer.android.com/develop/ui/views/notifications/notification-permission
+ // Already on Android 7 or newer, notifications can been turned off in system settings. This may happen
+ // while developers are testing an app, so also warn in this case.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !manager.areNotificationsEnabled()) {
+ Log.w(TAG, "To use the ObjectBox Admin keep-alive notification turn on notifications for this app");
+ }
+ Notification.Builder builder = buildBaseNotification(context, port, manager);
+ builder.setContentIntent(pendingIntent);
+ manager.notify(notificationId, builder.build());
+ }
+
+ return true;
+ }
+
+ static Notification.Builder buildBaseNotification(Context context, int port, NotificationManager manager) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // Note: IMPORTANCE_LOW so no sound is played to avoid distractions while testing.
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ "ObjectBox Admin", NotificationManager.IMPORTANCE_LOW);
+ // if channel already exists, create call will be ignored
+ manager.createNotificationChannel(channel);
+ }
+
+ Notification.Builder builder;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
+ } else {
+ builder = new Notification.Builder(context);
+ }
+
+ builder.setContentTitle(context.getString(R.string.objectbox_objectBrowserNotificationTitle))
+ .setContentText(context.getString(R.string.objectbox_objectBrowserNotificationText, port))
+ .setSmallIcon(R.drawable.objectbox_notification);
+
+ return builder;
+ }
+
+ static Intent viewIntent(String url) {
+ Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ viewIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ return viewIntent;
+ }
+
+ /**
+ * Targeting Android 12 requires to mark PendingIntents explicitly as immutable or mutable.
+ * https://developer.android.com/about/versions/12/behavior-changes-12#pending-intent-mutability
+ */
+ static int buildPendingIntentFlags(int flags) {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ return flags | PendingIntent.FLAG_IMMUTABLE;
+ } else {
+ return flags;
+ }
+ }
+
+}
diff --git a/objectbox-android/src/adminIncluded/java/io/objectbox/android/AdminKeepAliveService.java b/objectbox-android/src/adminIncluded/java/io/objectbox/android/AdminKeepAliveService.java
new file mode 100644
index 00000000..1d0f1c78
--- /dev/null
+++ b/objectbox-android/src/adminIncluded/java/io/objectbox/android/AdminKeepAliveService.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 ObjectBox Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.objectbox.android;
+
+import android.app.Notification;
+import android.app.Notification.Action.Builder;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.IBinder;
+import android.util.Log;
+
+import javax.annotation.Nullable;
+
+/**
+ * Foreground service to keep app alive which displays a notification to view {@link Admin} URL or stop this service.
+ */
+public class AdminKeepAliveService extends Service {
+
+ private static final String ACTION_STOP = "objectBox_keepAliveStop";
+
+ static final String EXTRA_KEY_PORT = "port";
+ static final String EXTRA_KEY_URL = "url";
+ static final String EXTRA_KEY_NOTIFICATION_ID = "notificationId";
+
+ private static final String TAG = AdminKeepAliveService.class.getSimpleName();
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (ACTION_STOP.equals(intent.getAction())) {
+ Log.d(TAG, "Stopping");
+ stopForeground(true);
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+ String url = intent.getStringExtra(EXTRA_KEY_URL);
+ int port = intent.getIntExtra(EXTRA_KEY_PORT, 0);
+ int notificationId = intent.getIntExtra(EXTRA_KEY_NOTIFICATION_ID, 0);
+
+ if (url != null && url.startsWith("http") && port > 0 && notificationId > 0) {
+ Intent stopIntent = new Intent(this, getClass());
+ stopIntent.setAction(ACTION_STOP);
+ PendingIntent stopPendingIntent = PendingIntent.getService(this, 0, stopIntent,
+ Admin.buildPendingIntentFlags(PendingIntent.FLAG_CANCEL_CURRENT));
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
+ Admin.viewIntent(url), Admin.buildPendingIntentFlags(0));
+
+ NotificationManager manager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ Notification.Builder builder = Admin.buildBaseNotification(this, port, manager);
+ builder.setContentIntent(pendingIntent);
+ // Actually useless because Foreground notifications cannot be deleted
+ builder.setDeleteIntent(stopPendingIntent);
+ builder.addAction(new Builder(R.drawable.objectbox_stop, "Stop", stopPendingIntent).build());
+
+ startForeground(notificationId, builder.getNotification());
+ Log.d(TAG, "Started");
+ return START_REDELIVER_INTENT; // with START_STICKY would not get intent on restart
+ } else {
+ Log.w(TAG, "Ignoring start command due to incomplete data");
+ return START_NOT_STICKY;
+ }
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/objectbox-android/src/adminIncluded/java/io/objectbox/android/AdminNotificationReceiver.java b/objectbox-android/src/adminIncluded/java/io/objectbox/android/AdminNotificationReceiver.java
new file mode 100644
index 00000000..51db3850
--- /dev/null
+++ b/objectbox-android/src/adminIncluded/java/io/objectbox/android/AdminNotificationReceiver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 ObjectBox Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.objectbox.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.Log;
+
+/**
+ * Handles actions of the {@link Admin} notification.
+ */
+public class AdminNotificationReceiver extends BroadcastReceiver {
+
+ private static final String TAG = AdminNotificationReceiver.class.getSimpleName();
+ static final String ACTION_KEEP_ALIVE = "io.objectbox.action.KEEP_ALIVE";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!ACTION_KEEP_ALIVE.equals(intent.getAction())) {
+ return;
+ }
+ if (!intent.hasExtra(AdminKeepAliveService.EXTRA_KEY_URL)) {
+ Log.w(TAG, "Ignoring keep alive intent due to incomplete data");
+ return;
+ }
+
+ // start foreground service to keep app process alive
+ Intent serviceIntent = new Intent(context, AdminKeepAliveService.class);
+ serviceIntent.putExtras(intent);
+ if (VERSION.SDK_INT >= VERSION_CODES.O) {
+ context.startForegroundService(serviceIntent);
+ } else {
+ context.startService(serviceIntent);
+ }
+
+ // launch browser
+ String url = intent.getStringExtra(AdminKeepAliveService.EXTRA_KEY_URL);
+ context.startActivity(Admin.viewIntent(url));
+ }
+
+}
diff --git a/objectbox-android/src/adminIncluded/java/io/objectbox/android/AndroidObjectBrowser.java b/objectbox-android/src/adminIncluded/java/io/objectbox/android/AndroidObjectBrowser.java
new file mode 100644
index 00000000..f5651a56
--- /dev/null
+++ b/objectbox-android/src/adminIncluded/java/io/objectbox/android/AndroidObjectBrowser.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 ObjectBox Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.objectbox.android;
+
+import android.content.Context;
+
+import io.objectbox.BoxStore;
+
+/**
+ * A helper class to start the ObjectBox Admin web app used to browse and gain insights into the database.
+ *
+ * Usage requires manually configuring some ObjectBox dependencies, see the
+ * documentation for more details.
+ *
+ *
+ * if (BuildConfig.DEBUG) {
+ * boolean started = new AndroidObjectBrowser(boxStore).start(this);
+ * Log.i("ObjectBoxAdmin", "Started: " + started);
+ * }
+ *
+ * After {@link #start} is called a notification is displayed. Tap it to open this Admin URL on the device.
+ * Alternatively, look for a logcat message from Admin to obtain the URL. Use {@code adb forward} to access
+ * the URL on your development machine.
+ *
+ * Tapping the notification starts a foreground service to keep this app running in the background.
+ * Stop this keep-alive service from the notification.
+ *
+ * See the web documentation for details.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+@Deprecated
+public class AndroidObjectBrowser extends Admin {
+
+ /**
+ * Creates a helper to control Admin for the given {@code boxStore}.
+ *
+ * See the {@link AndroidObjectBrowser class documentation} for details.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public AndroidObjectBrowser(BoxStore boxStore) {
+ super(boxStore);
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public int getNotificationId() {
+ return super.getNotificationId();
+ }
+
+ /**
+ * The id passed to {@link android.app.NotificationManager#notify} for the Admin notification.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public void setNotificationId(int notificationId) {
+ super.setNotificationId(notificationId);
+ }
+
+ /**
+ * Starts the ObjectBox Admin HTTP server.
+ *
+ * See the {@link AndroidObjectBrowser class documentation} for details.
+ *
+ * @deprecated Use {@link Admin} instead.
+ */
+ @Deprecated
+ public boolean start(Context context) {
+ return super.start(context);
+ }
+}
diff --git a/objectbox-android/src/adminIncluded/res/drawable-hdpi/objectbox_notification.png b/objectbox-android/src/adminIncluded/res/drawable-hdpi/objectbox_notification.png
new file mode 100644
index 00000000..84236e32
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-hdpi/objectbox_notification.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-hdpi/objectbox_stop.png b/objectbox-android/src/adminIncluded/res/drawable-hdpi/objectbox_stop.png
new file mode 100644
index 00000000..dfff26ce
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-hdpi/objectbox_stop.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-mdpi/objectbox_notification.png b/objectbox-android/src/adminIncluded/res/drawable-mdpi/objectbox_notification.png
new file mode 100644
index 00000000..da2b3038
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-mdpi/objectbox_notification.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-mdpi/objectbox_stop.png b/objectbox-android/src/adminIncluded/res/drawable-mdpi/objectbox_stop.png
new file mode 100644
index 00000000..9f6dc9ef
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-mdpi/objectbox_stop.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-xhdpi/objectbox_notification.png b/objectbox-android/src/adminIncluded/res/drawable-xhdpi/objectbox_notification.png
new file mode 100644
index 00000000..0719e380
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-xhdpi/objectbox_notification.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-xhdpi/objectbox_stop.png b/objectbox-android/src/adminIncluded/res/drawable-xhdpi/objectbox_stop.png
new file mode 100644
index 00000000..3ad2c9c4
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-xhdpi/objectbox_stop.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-xxhdpi/objectbox_notification.png b/objectbox-android/src/adminIncluded/res/drawable-xxhdpi/objectbox_notification.png
new file mode 100644
index 00000000..97af3800
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-xxhdpi/objectbox_notification.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-xxhdpi/objectbox_stop.png b/objectbox-android/src/adminIncluded/res/drawable-xxhdpi/objectbox_stop.png
new file mode 100644
index 00000000..801d3411
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-xxhdpi/objectbox_stop.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-xxxhdpi/objectbox_notification.png b/objectbox-android/src/adminIncluded/res/drawable-xxxhdpi/objectbox_notification.png
new file mode 100644
index 00000000..a6bc5520
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-xxxhdpi/objectbox_notification.png differ
diff --git a/objectbox-android/src/adminIncluded/res/drawable-xxxhdpi/objectbox_stop.png b/objectbox-android/src/adminIncluded/res/drawable-xxxhdpi/objectbox_stop.png
new file mode 100644
index 00000000..52393366
Binary files /dev/null and b/objectbox-android/src/adminIncluded/res/drawable-xxxhdpi/objectbox_stop.png differ
diff --git a/objectbox-android/src/adminIncluded/res/values/strings.xml b/objectbox-android/src/adminIncluded/res/values/strings.xml
new file mode 100644
index 00000000..d6de685e
--- /dev/null
+++ b/objectbox-android/src/adminIncluded/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+
+ ObjectBox Admin
+
+ Running on port %1$d. Tap to open and keep app running.
+
diff --git a/objectbox-android/src/main/AndroidManifest.xml b/objectbox-android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..44cfa242
--- /dev/null
+++ b/objectbox-android/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/objectbox-android/src/main/java/io/objectbox/android/AndroidScheduler.java b/objectbox-android/src/main/java/io/objectbox/android/AndroidScheduler.java
new file mode 100644
index 00000000..ac2c358c
--- /dev/null
+++ b/objectbox-android/src/main/java/io/objectbox/android/AndroidScheduler.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 ObjectBox Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.objectbox.android;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+import androidx.annotation.NonNull;
+import io.objectbox.reactive.RunWithParam;
+import io.objectbox.reactive.Scheduler;
+
+/**
+ * A Looper-based Scheduler implementation, see {@link #mainThread()} for most common usage.
+ */
+public class AndroidScheduler extends Handler implements Scheduler {
+ private static AndroidScheduler MAIN_THREAD;
+
+ /**
+ * Returns a Scheduler that runs tasks on Android's main thread.
+ */
+ public static synchronized Scheduler mainThread() {
+ if (MAIN_THREAD == null) {
+ MAIN_THREAD = new AndroidScheduler(Looper.getMainLooper());
+ }
+ return MAIN_THREAD;
+ }
+
+ private final Deque freeRunners = new ArrayDeque<>();
+
+ /**
+ * If you run your own Looper, you can create a custom Scheduler using it.
+ */
+ public AndroidScheduler(Looper looper) {
+ super(looper);
+ }
+
+ // Note: need to cast to RunWithParam