Only this pageAll pages
Powered by GitBook
1 of 30

ObjectBox Docs

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

ObjectBox Docs

Explore the on-device database and SQLite alternative for object and vector data. This is the official ObjectBox documentation for Java/Kotlin, Dart/Flutter, and Python.

We're hiring! We believe resource-efficient coding is still cool 😎 and are looking for a C / C++ developer who shares our sentiment.

C++ and Swift: Check the docs in https://cpp.objectbox.io/ and https://swift.objectbox.io/.

🚀 Our mission is to revolutionize mobile app development by making it fast and efficient with our intuitive and user-friendly native-language APIs. With ObjectBox, you can easily store and synchronize objects in various programming languages such as Python, Java, C, Go, Kotlin, Dart, C++, and Swift. It works seamlessly across different platforms like Android, iOS, Windows, and Linux, and you can even use it with Docker.

💚 ! Community feedback is very important for us as we strive to make ObjectBox better for our users. Our feedback form is anonymous and only takes 2 minutes to fill in. Every response is highly appreciated. Thank you in advance! 🙏

To rate this documentation, use the "Was this page helpful?" smiley 🙃 at the end of each page.

🚩 Feel free to open an issue on GitHub (for or ) or send us your comments to contact[at]objectbox.io - Thank you! - and if you like what you see, we also appreciate a shout out :) or GitHub star ⭐

Please share your feedback
👍
Java & Kotlin
Dart/Flutter
Getting started

Android (Java/Kotlin)

ObjectBox is an Android database designed for Edge Computing and Offline First experiences. Here is the How-to and troubleshooting guides for using ObjectBox on Android with Java or Kotlin.

Learn all about Local Unit Tests, LiveData, Paging, and App Bundle. Please let us know, if our developer resources are helpful for you: The smiley at the bottom of the page already gives us an indication.

Paging (Arch. Comp.)

The Android Paging Library helps you load and display small data chunks at a time. Learn to use ObjectBox database with the Paging library from Android Architecture Components.

Since 2.0.0

ObjectBox supports integration with the Paging library that is part of Google's Android Architecture Components. To that end, the ObjectBox Android library (objectbox-android) provides the ObjectBoxDataSource class. It is an implementation of the Paging library's PositionalDataSource.

Note: the following assumes that you have already in your project.

Using ObjectBoxDataSource

Within your ViewModel, similar to , you first . But then, you construct an ObjectBoxDataSource factory with it instead. This factory is then passed to a LivePagedListBuilder to build the actual LiveData.

Here is an example of a ViewModel class doing just that:

Note that the LiveData holds your entity class, here Note, wrapped inside a PagedList. You observe the LiveData as usual in your activity or fragment, then submit the PagedList on changes to your PagedListAdapter of the Paging library.

We will not duplicate how this works here, see the for details about this.

Next steps

  • Have a look at the .

  • Check out .

  • Learn how to .

LiveData (Arch. Comp.)

LiveData is an observable data holder class. Learn to use ObjectBox database with LiveData from Android Architecture Components.

ObjectBox - LiveData with Android Architecture Components

Since 1.2.0. Have a look at .

greenDAO Compat

Use existing greenDAO code with ObjectBox and migrate from greenDAO to ObjectBox.

Do you have an existing app that already uses the greenDAO library? We created DaoCompat as a compatibility layer that gives you a greenDAO like API, but behind the scenes uses ObjectBox to store your app's data.

Check out the on how to easily migrate your app to ObjectBox. There is also an available.

Feel free to , or if you encounter a bug with DaoCompat .

DaoCompat documentation
example app
ask for help on Stack Overflow
create an issue

Advanced

Guides and documentation for advanced use cases of ObjectBox.

added and set up the Paging library
creating a LiveData directly
build your ObjectBox query
Paging library documentation
ObjectBox Architecture Components example code
ObjectBox support for LiveData
build queries
As an alternative to ObjectBox’
, you can opt for the
approach supplied by Android Architecture Components. ObjectBox comes with ObjectBoxLiveData, a class that can be used inside your
classes.

A simple ViewModel implementation for our note example app includes the special ObjectBoxLiveData that is constructed using a regular ObjectBox query:

Note that we did choose to pass the box to getNoteLiveData() . Instead you could use AndroidViewModel , which provides access to the Application context, and then call ((App)getApplication()).getBoxStore().boxFor() inside the ViewModel. However, the first approach has the advantage that our ViewModel has no reference to Android classes. This makes it easier to unit test.

Now, when creating the activity or fragment we get the ViewModel, access its LiveData and finally register to observe changes:

The ObjectBoxLiveData will now subscribe to the query and notify observers when the results of the query change, if there is at least one observer. In this example the activity is notified if a note is added or removed. If all observers are destroyed, the LiveData will cancel the subscription to the query.

If you have used ObjectBox observers in the past this might sound familiar. Well, because it is! ObjectBoxLiveData just wraps a DataObserver on the query you give to it.

the example project on GitHub
data observers and reactive queries
LiveData
ViewModel
public class NotePagedViewModel extends ViewModel {
    
    private LiveData<PagedList<Note>> noteLiveDataPaged;
    
    public LiveData<PagedList<Note>> getNoteLiveDataPaged(Box<Note> notesBox) {
        if (noteLiveDataPaged == null) {
            // query all notes, sorted a-z by their text
            Query<Note> query = notesBox.query().order(Note_.text).build();
            // build LiveData
            noteLiveDataPaged = new LivePagedListBuilder<>(
                    new ObjectBoxDataSource.Factory<>(query),
                    20 /* page size */
            ).build();
        }
        return noteLiveDataPaged;
    }
}
public class NoteViewModel extends ViewModel {
    
    private ObjectBoxLiveData<Note> noteLiveData;
    
    public ObjectBoxLiveData<Note> getNoteLiveData(Box<Note> notesBox) {
        if (noteLiveData == null) {
            // query all notes, sorted a-z by their text
            noteLiveData = new ObjectBoxLiveData<>(notesBox.query().order(Note_.text).build());
        }
        return noteLiveData;
    }
}
NoteViewModel model = ViewModelProviders.of(this).get(NoteViewModel.class);
model.getNoteLiveData(notesBox).observe(this, new Observer<List<Note>>() {
    @Override
    public void onChanged(@Nullable List<Note>; notes) {
        notesAdapter.setNotes(notes);
    }
});

Desktop Apps

Besides Android apps, ObjectBox for Java supports desktop apps running on Linux, macOS and Windows written in Java or Kotlin. See how to build and test desktop apps using ObjectBox.

ObjectBox – Embedded Database for Java Desktop Apps

Just like on Android, ObjectBox stands for a super simple API and high performance. It’s designed for objects and outperforms other database and ORM solutions. Because it is an embedded database, ObjectBox runs in your apps’ process and needs no maintenance. Read on to learn how to create a Java project using ObjectBox. We believe it’s fairly easy. Please let us know your thoughts on it.

Setup and Usage

See the Getting Started page on how to set up your project, add entities and use the ObjectBox APIs.

Examples

There are example command line apps available in .

Building Unit Tests

The setup and writing tests is identical to writing unit tests that run on the local JVM for Android, see .

Changelogs

The changelogs for each programming language are available at GitHub:

For other languages, please check there respective doc pages:

Getting started
our examples repository
Android Local Unit Tests

C and C++

  • Swift

  • Go

  • Changelog History before 4.0.3

    Older entries (4.0.2 and earlier) are available in the release history.

    Java changelog (GitHub releases)
    Dart changelog
    Python changelog (GitHub releases)

    App Bundle, split APKs and LinkageError

    Troubleshoot or avoid crashes when using ObjectBox db and Android App Bundle or due to buggy devices. Google-certified devices prevent this crash.

    Your app might observe crashes due to UnsatisfiedLinkError or (since ObjectBox 2.3.4) LinkageError on some devices. This has mainly two reasons:

    • If your app uses the App Bundle format, the legacy split APK feature or Multidex the native library can't be found.

    • Or if your app's minimum SDK level is below API 23 (Marshmallow), there are known bugs in Android's native library loading code.

    Let us know if the below suggestions do not resolve your crashes in .

    App Bundle and split APKs

    When using an App Bundle or split APKs Google Play only delivers the split APKs required for each user's device configuration, including its architecture (ABI). If users bypass Google Play to install your app ("sideloading") they might not install all of the required split APKs. If the split APK containing the ObjectBox native library required for the device ABI is missing, your app will crash with LinkageError when building BoxStore.

    Using the Play Core library to check for missing splits

    Update August 2020: Google-certified devices (those running Google Play Services) or those running Android 10 (API level 29) or higher prevent users from sideloading split APKs, preventing this crash. Adding the below check is no longer necessary for these devices.

    Add the to the dependencies block:

    In the Application class add the missing split APKs check before calling super.onCreate():

    If a broken installation is detected, users will see a message and Reinstall button asking them to re-install the app from Google Play.

    See to use the Play Core library detection.

    If the Play Core library should not be used, there are two alternatives:

    Alternative: Catch exception and inform users

    You can guard the MyObjectBox build call and for example display an activity with an info message (e.g. direct users to reinstall the app from Google Play, send you an error report, ...):

    As an example see .

    Alternative: turn off splitting by ABI

    The simplest solution is to always include native libraries for all supported ABIs. However, this will increase the download size of your app for all users.

    Source:

    Buggy devices (API 22 or lower)

    On some devices and if your minimum SDK is below API 23 (Android 6.0 Marshmallow), loading the native library may fail with LinkageError due to known bugs in Android's native library loading code. To counter this ObjectBox includes support for the tool which will try to extract the native library manually if loading it normally fails.

    To enable this, just add ReLinker to your dependencies:

    ObjectBox is calling ReLinker via reflection. If you are using ProGuard or Multidex, make sure to add keep rules so that ReLinker code is not stripped from the final app or is not in the primary dex file.

    For ProGuard add this line:

    For Multidex add a multiDexKeepProguard file to your build file:

    And in the multidex-config.pro file add the same rule as above:

    . We are using the ProGuard format (multiDexKeepProguard property). You can also use the multiDexKeepFile property, but make sure to adapt the rule above to that format.

    Enable ReLinker debug log

    To enable debug logs for ReLinker you can pass a custom ReLinkerInstance when building BoxStore:

    GitHub issue 605
    Play Core library
    how we updated our example app
    how we added this to our Android app example
    Android Developers
    ReLinker
    Multidex supports two file formats to keep files
    implementation 'com.google.android.play:core:1.7.3'
    public class App extends Application {
    
        @Override
        public void onCreate() {
            if (MissingSplitsManagerFactory.create(this).disableAppIfMissingRequiredSplits()) {
                return; // Skip app initialization.
            }
            super.onCreate();
            // ...
        }
    }
    // guard the build call and set some flag (here setting the boxStore field null)
    try {
        boxStore = MyObjectBox.builder()
                .androidContext(context.getApplicationContext())
                .build();
    } catch (LinkageError e) {
        boxStore = null;
        Log.e(App.TAG, "Failed to load ObjectBox: " + e.getMessage());
    }
    
    
    // then for example in the main activity check the flag in onCreate and 
    // direct to an info/error message without the app crashing:
    if (ObjectBox.get() == null) {
        startActivity(new Intent(this, ErrorActivity.class));
        finish();
        return;
    }
    android {
        bundle {
            abi {
                // This property is set to true by default.
                enableSplit = false
            }
        }
    }
    // https://github.com/KeepSafe/ReLinker/releases
    implementation 'com.getkeepsafe.relinker:relinker:1.4.1'
    -keep class com.getkeepsafe.relinker.** { *; }
    android {
        buildTypes {
            release {
                multiDexKeepProguard file('multidex-config.pro')
            }
        }
    }
    -keep class com.getkeepsafe.relinker.** { *; }
    boxStore = MyObjectBox.builder()
        .androidContext(App.this)
        .androidReLinker(ReLinker.log(new ReLinker.Logger() {
            @Override
            public void log(String message) { Log.d(TAG, message); }
        }))
        .build();

    Object IDs

    Explanation of Object IDs and how they are used and assigned in ObjectBox.

    ObjectBox - Object IDs

    Objects must have an ID property of type long. You are free to use the wrapper type java.lang.Long, but we advise against it in most cases. long IDs are enforced to make ObjectBox very efficient internally.

    If your application requires other ID types (such as a string UID given by a server), you can model them as standard properties and use to look up entities by your application-specific ID.

    Object ID: new vs. persisted entities

    When you create new entity objects (on the language level), they are not persisted yet and their ID is (zero). Once an entity is put (persisted), ObjectBox will assign an ID to the entity. You can access the ID property right after the call to put().

    Those are also applied the other way round: ObjectBox uses the ID as a state indication of whether an entity is new (zero) or already persisted (non-zero). This is used internally, e.g. for relations that heavily rely on IDs.

    Special Object IDs

    Object IDs may be any long value, with two exceptions:

    • 0 (zero): Objects with an ID of zero (and null if the ID is of type Long) are considered new (not persisted before). Putting such an object will always insert a new object and assign an unused ID to it.

    • 0xFFFFFFFFFFFFFFFF (-1 in Java): This value is reserved for internal use by ObjectBox and may not be used by the app.

    Object ID assignment (default)

    By default, object IDs are assigned by ObjectBox. For each new object, ObjectBox will assign an unused ID that is above the current highest ID value used in a box. For example, if there are two objects with ID 1 and ID 100 in a box the next object that is put will be assigned ID 101.

    Also, note that this will mean in some circumstances IDs from deleted objects may be reused. So it is best to not rely on a specific ID getting assigned.

    By default, only ObjectBox may assign IDs. If you try to put an object with an ID greater than the currently highest ID, ObjectBox will throw an error.

    Manually assigned Object IDs

    This is not recommended. Check if using a is a viable alternative.

    If your code needs to assign IDs by itself you can change the @Id annotation to:

    This will allow putting an entity with any valid ID (see ). If the @Id field is writable, it can still be set to zero to let ObjectBox auto-assign a new ID.

    Warning: manually assigning IDs breaks automatic state detection (new vs. persisted entity based on the ID). Therefore, entities with manually assigned IDs should be put immediately and the Box may have to be attached manually, especially when working with relations.

    For details see the documentation about .

    String ID alias (future work)

    Check on Github for status.

    queries
    unique indexed property
    updating relations
    this issue
    Special Object IDs
    @Id(assignable = true)
    long id;
    @Id(assignable: true)
    int id = 0;

    Transactions

    ObjectBox is a fully transactional database satisfying ACID properties. ObjectBox database gives you an easy way to develop safe and efficient data applications; single or multi-threaded.

    ObjectBox - Transactions

    A transaction can group several operations into a single unit of work that either executes completely or not at all. If you are looking for a more detailed introduction to transactions in general, please consult other resources like Wikipedia on database transactions. For ObjectBox transactions continue reading:

    You may not notice it, but almost all interactions with ObjectBox involve transactions. For example, if you call put a write transaction is used. Also if you get an object or query for objects, a read transaction is used. All of this is done under the hood and transparent to you. It may be fine to completely ignore transactions altogether in your app without running into any problems. With more complex apps however, it’s usually worth learning transaction basics to make your app more consistent and efficient.

    TL;DR - a quick summary on Transactions

    • Accessing data always happens inside an implicit transaction, the API hides this detail for convenience.

    • You should use an explicit transaction for non-trivial operations for better speed and atomicity.

    • Transactions manage multi-threading; e.g. a transaction is tied to a thread and vice versa.

    • Read(-only) transactions never get blocked or block a write transaction.

    Explicit Transactions

    We learned that all ObjectBox operations run in implicit transactions – unless an explicit transaction is in progress. In the latter case, multiple operations share the (explicit) transaction. In other words, with explicit transactions, you control the transaction boundary. Doing so can greatly improve efficiency and consistency in your app.

    The advantage of explicit transactions over the bulk put operations is that you can perform any number of operations and use objects of multiple boxes. In addition, you get a consistent (transactional) view on your data while the transaction is in progress.

    Example for a write transaction:

    The class offers the following methods to perform explicit transactions:

    • runInTx: Runs the given runnable inside a transaction.

    • runInReadTx: Runs the given runnable inside a read(-only) transaction. Unlike write transactions, multiple read transactions can run at the same time.

    • runInTxAsync: Runs the given Runnable as a transaction in a separate thread. Once the transaction completes the given callback is called (callback may be null).

    Transaction Costs

    Understanding transactions is essential to master database performance. If you just remember one sentence on this topic, it should be this one: a write transaction has its price.

    Committing a transaction involves syncing data to physical storage, which is a relatively expensive operation for databases. Only when the file system confirms that all data has been stored in a durable manner (not just memory cached), the transaction can be considered successful. This file sync required by a transaction may take a couple of milliseconds. Keep this in mind and try to group several operations (e.g.putcalls) in one transaction.

    Consider this example:

    Do you see the performance issue with that code? An implicit write transaction is created for each user. This will consume a lot of resources, especially for a high number of user objects. It is much more efficient to put all users at once, in a single transaction:

    Much better! Even if you would have a database with 1,000 users, the latter example would use a single write transaction to store all user objects at once. The first code example would use 1,000 (!) implicit transactions, causing a massive slow down.

    Read Transactions

    In ObjectBox, read transactions are cheap. In contrast to write transactions, there is no commit and thus no expensive sync to the file system. Operations like get , count , and queries run inside an implicit read transaction if they are not called when already inside an explicit transaction (read or write). Note that it is illegal to put when inside a read transaction: an exception will be thrown.

    While read transactions are much cheaper than write transactions, there is still some overhead to starting a read transaction. Thus, for a high number of reads (e.g. hundreds, in a loop), you can improve performance by grouping those reads in a single read transaction (see explicit transactions below).

    Multiversion Concurrency

    ObjectBox gives developers semantics. This allows multiple concurrent readers (read transactions) which can execute immediately without blocking or waiting. This is guaranteed by storing multiple versions of (committed) data. Even if a write transaction is in progress, a read transaction can read the last consistent state immediately. Write transactions are executed sequentially to ensure a consistent state. Thus, it is advised to keep write transactions short to avoid blocking other pending write transactions. For example, it is usually a bad idea to do networking or complex calculations while inside a write transaction. Instead, do any expensive operation and prepare objects before entering a write transaction.

    Note that you do not have to worry about making write transactions sequential yourself. If multiple threads want to write at the same time (e.g. via put or runInTx), one of the threads will be selected to go first, while the other threads have to wait. It works just like a lock or synchronized in Java.

    Locking inside a Write Transaction

    Avoid locking (e.g. via synchronized or java.util.concurrent.locks) when inside a write transaction when possible. Because write transactions run exclusively, they effectively acquire a write lock internally. As with all locks, you need to pay close attention when multiple locks are involved. Always obtain locks in the same order to avoid deadlocks. If you acquire a lock “X” inside a transaction, you must ensure that your code does not start another write transaction while having the lock “X”.

    Android Local Unit Tests

    How to create ObjectBox local unit tests for Android projects.

    Android Local Unit Tests

    ObjectBox supports local unit tests. This gives you the full ObjectBox functionality for running super fast test directly on your development machine.

    On can either run on an Android device (or emulator), so called instrumented tests, or they can run on your local development machine. Running local unit tests is typically much faster.

    To learn how local unit tests for Android work in general have a look at the Android developers documentation on . Read along to learn how to use ObjectBox in your local unit tests.

    This page also applies to writing unit tests for desktop projects.

    Set Up Your Testing Environment

    The setup step is only required for ObjectBox 1.4 or older (or if you want to manually add the dependencies). In newer versions the ObjectBox plugin automatically adds the native ObjectBox library required for your current operating system.

    Add the native ObjectBox library for your operating system to your existing test dependencies in your app’s build.gradle file:

    The ObjectBox native libraries currently only support 64-bit desktop operating systems.

    On Windows you might have to install the Microsoft Visual C++ 2015 Redistributable (x64) packages to use the native library.

    Create a Local Unit Test Class

    You create your local unit test class as usual under module-name/src/test/java/. To use ObjectBox in your test methods you need to build a BoxStore instance using the generated MyObjectBox class of your project. You can use the directory(File) method on the BoxStore builder to ensure the test database is stored in a specific folder on your machine. To start with a clean database for each test you can delete the existing database using BoxStore.deleteAllFiles(File).

    The following example shows how you could implement a local unit test class that uses ObjectBox:

    To help diagnose issues you can enable log output for ObjectBox actions, such as queries, by specifying one or more debug flags when building BoxStore.

    Base class for tests

    It’s usually a good idea to extract the setup and tear down methods into a base class for your tests. E.g.:

    Testing Entities with Relations

    Kotlin desktop projects only. Since 3.0.0 this is no longer necessary for Android projects (either Kotlin or Java), initialization magic works for them now as well.

    To test entities that have relations, like ToOne or ToMany properties, on the local JVM you must initialize them and add a transient BoxStore field.

    See the documentation about "initialization magic" for an example and what to look out for.

    Background: the "initialization magic" is normally done by the ObjectBox plugin using the Android Gradle Plugin Transform API or a Gradle task running after the Java compiler which allows to modify byte-code. However, this does currently not work for Kotlin code in Kotlin desktop projects.

    Android, unit tests
    Building Local Unit Tests
    public class NoteTest {
        
        private static final File TEST_DIRECTORY = new File("objectbox-example/test-db");
        private BoxStore store;
        
        @Before
        public void setUp() throws Exception {
            // Delete any files in the test directory before each test to start with a clean database.
            BoxStore.deleteAllFiles(TEST_DIRECTORY);
            store = MyObjectBox.builder()
                    // Use a custom directory to store the database files in.
                    .directory(TEST_DIRECTORY)
                    // Optional: add debug flags for more detailed ObjectBox log output.
                    .debugFlags(DebugFlags.LOG_QUERIES | DebugFlags.LOG_QUERY_PARAMETERS)
                    .build();
        }
        
        @After
        public void tearDown() throws Exception {
            if (store != null) {
                store.close();
                store = null;
            }
            BoxStore.deleteAllFiles(TEST_DIRECTORY);
        }
        
        @Test
        public void exampleTest() {
            // get a box and use ObjectBox as usual
            Box<Note> noteBox = store.boxFor(Note.class);
            assertEquals(...);
        }
        
    }
    open class NoteTest {
    
        private var _store: BoxStore? = null
        protected val store: BoxStore
            get() = _store!!
    
        @Before
        fun setUp() {
            // Delete any files in the test directory before each test to start with a clean database.
            BoxStore.deleteAllFiles(TEST_DIRECTORY)
            _store = MyObjectBox.builder()
                // Use a custom directory to store the database files in.
                .directory(TEST_DIRECTORY)
                // Optional: add debug flags for more detailed ObjectBox log output.
                .debugFlags(DebugFlags.LOG_QUERIES or DebugFlags.LOG_QUERY_PARAMETERS)
                .build()
        }
    
        @After
        fun tearDown() {
            _store?.close()
            _store = null
            BoxStore.deleteAllFiles(TEST_DIRECTORY)
        }
        
        @Test
        fun exampleTest() {
            // Get a box and use ObjectBox as usual
            val noteBox = store.boxFor(Note::class.java)
            assertEquals(...)
        }
    
        companion object {
            private val TEST_DIRECTORY = File("objectbox-example/test-db")
        }
    }
    public class AbstractObjectBoxTest {
    
        private static final File TEST_DIRECTORY = new File("objectbox-example/test-db");
    
        protected BoxStore store;
    
        @Before
        public void setUp() throws Exception {
            // Delete any files in the test directory before each test to start with a clean database.
            BoxStore.deleteAllFiles(TEST_DIRECTORY);
            store = MyObjectBox.builder()
                    // Use a custom directory to store the database files in.
                    .directory(TEST_DIRECTORY)
                    // Optional: add debug flags for more detailed ObjectBox log output.
                    .debugFlags(DebugFlags.LOG_QUERIES | DebugFlags.LOG_QUERY_PARAMETERS)
                    .build();
        }
    
        @After
        public void tearDown() throws Exception {
            if (store != null) {
                store.close();
                store = null;
            }
            BoxStore.deleteAllFiles(TEST_DIRECTORY);
        }
    }
    open class AbstractObjectBoxTest {
    
        private var _store: BoxStore? = null
        protected val store: BoxStore
            get() = _store!!
    
        @Before
        fun setUp() {
            // Delete any files in the test directory before each test to start with a clean database.
            BoxStore.deleteAllFiles(TEST_DIRECTORY)
            _store = MyObjectBox.builder()
                // Use a custom directory to store the database files in.
                .directory(TEST_DIRECTORY)
                // Optional: add debug flags for more detailed ObjectBox log output.
                .debugFlags(DebugFlags.LOG_QUERIES or DebugFlags.LOG_QUERY_PARAMETERS)
                .build()
        }
    
        @After
        fun tearDown() {
            _store?.close()
            _store = null
            BoxStore.deleteAllFiles(TEST_DIRECTORY)
        }
    
        companion object {
            private val TEST_DIRECTORY = File("objectbox-example/test-db")
        }
    }
    dependencies {
        // Required -- JUnit 4 framework
        testImplementation("junit:junit:4.12")
        // Optional -- manually add native ObjectBox library to override auto-detection
        testImplementation("io.objectbox:objectbox-linux:$objectboxVersion")
        testImplementation("io.objectbox:objectbox-macos:$objectboxVersion")
        testImplementation("io.objectbox:objectbox-windows:$objectboxVersion")
        // Not added automatically:
        // Since 2.9.0 we also provide ARM support for the Linux library
        testImplementation("io.objectbox:objectbox-linux-arm64:$objectboxVersion")       
        testImplementation("io.objectbox:objectbox-linux-armv7:$objectboxVersion")
    }
  • There can only be a single write transaction at any time; they run strictly one after the other (sequential).

  • Sequential execution simplifies user code that is run in write transactions and makes it safer.

  • Keep write transactions short to optimize throughput, e.g. prepare data before entering it.

  • callInTx: Like runInTx(Runnable), but allows returning a value and throwing an exception.

  • The class BoxStore offers the following methods to perform explicit transactions:

    • runInTx: Runs the given runnable inside a transaction.

    • runInReadTx: Runs the given runnable inside a read(-only) transaction. Unlike write transactions, multiple read transactions can run at the same time.

    • runInTxAsync: Runs the given Runnable as a transaction in a separate thread. Once the transaction completes the given callback is called (callback may be null).

    • callInTx: Like runInTx(Runnable), but allows returning a value and throwing an exception.

    The class Store offers the following methods to perform explicit transactions:

    • runInTransaction: Runs the given function inside a read or write transaction, depending on the given mode. Note that unlike write transactions, multiple read transactions can run at the same time.

    • runInTransactionAsync: Spawns an isolate to run the given callback within a read or write transaction, depending on the given mode.

    The Store class provides read_tx and write_tx methods for creating read/write transactions which should be called in a with statement:

    BoxStore
    Multiversion concurrency control (MVCC)

    Data Model Updates

    How to rename entities and properties, change property types in ObjectBox.

    ObjectBox - Data Model Updates

    ObjectBox manages its data model (schema) mostly automatically. The data model is defined by the entity classes you define. When you add or remove entities or properties of your entities, ObjectBox takes care of those changes without any further action from you.

    For other changes like renaming or changing the type, ObjectBox needs extra information to make things unambiguous. This is done by setting a unique ID (UIDs) as an annotation, as we will see below.

    UIDs

    ObjectBox keeps track of entities and properties by assigning them unique IDs (UIDs). All those UIDs are stored in a file objectbox-models/default.json (Java, Kotlin) or lib/objectbox-model.json (Dart) which you should add to your version control system (e.g. git). If you are interested, we have . But let’s continue with how to rename entities or properties.

    In short: To make UID-related changes, put an @Uid annotation (Java, Kotlin) or @Entity(uid: 0)/@Property(uid: 0) (Dart) on the entity or property and build the project to get further instructions. Repeat for each entity or property to change.

    Renaming Entities and Properties

    So why do we need that UID annotation? If you simply rename an entity class, ObjectBox only sees that the old entity is gone and a new entity is available. This can be interpreted in two ways:

    • The old entity is removed and a new entity should be added, the old data is discarded. This is the default behavior of ObjectBox.

    • The entity was renamed, the old data should be re-used.

    So to tell ObjectBox to do a rename instead of discarding your old entity and data, you need to make sure it knows that this is the same entity and not a new one. You do that by attaching the internal UID to the entity.

    The same is true for properties.

    Now let’s walk through how to do that. The process works the same if you want to rename a property:

    How-to and Example (Java/Kotlin and Dart)

    Step 1: Add an empty UID to the entity/property you want to rename:

    Step 2: Build the project (in Dart, run pub run build_runner build). The build will fail with an error message that gives you the current UID of the entity/property:

    Step 3: Apply the UID from the [Rename] section of the error message to your entity/property:

    Step 4: The last thing to do is the actual rename on the language level (Java, Kotlin, etc.):

    Step 5: Build the project again, it should now succeed. You can now use your renamed entity/property as expected and all existing data will still be there.

    Repeat the steps above to rename another entity or property.

    Note: Instead of the above you can also find the UID of the entity/property in the model JSON mentioned in the introduction. You can add the UID value to the annotation yourself, before renaming the entity/property, and skip the intermediate error where ObjectBox just prints it for you. This can be faster when renaming multiple properties.

    How-to and Example (Python)

    Since in Python there isn't a build step, the steps for renaming an Entity are different. Say we want to rename the Entity MyName to MyNewName:

    Step 1: Find out the UID of the entity MyName:

    Step 2: Rename MyName to MyNewName and explicitly specify the old UID:

    This makes possible for Objectbox to associate the old entity to the new one, and retain persisted data. If you don't specify the old UID, Objectbox will discard the old data and add a fresh new entity called MyNameName to the schema.

    Changing Property Types

    ObjectBox does not support migrating existing property data to a new type. You will have to take care of this yourself, e.g. by keeping the old property and adding some migration logic.

    There are two solutions to changing the type of a property:

    • Add a new property with a different name (this only works if the property has no @Uid annotation already):

    • Set a new UID for the property so ObjectBox treats it as a new property. Let’s walk through how to do that:

    How-to and Example

    Step 1: Add the @Uid annotation to the property where you want to change the type:

    Step 2: Build the project. The build will fail with an error message that gives you a newly created UID value:

    Step 3: Apply the UID from the [Change/reset] section to your property:

    Step 4: Build the project again, it should now succeed. You can now use the property in your entity as if it were a new one.

    Repeat the steps above to change the type of another property.

    Entity Inheritance

    How to inherit properties from entity super classes.

    ObjectBox - Entity Super Classes

    Only available for Java/Kotlin at the moment

    ObjectBox allows entity inheritance to share persisted properties in super classes. The base class can be an entity or non-entity class. For this purpose the @Entity annotation is complemented by the @BaseEntity annotation. There are three types of super classes, which are defined via annotations:

    • No annotation: The base class and its properties are not considered for persistence.

    • @BaseEntity: Properties are considered for persistence in sub classes, but the base class itself cannot be persisted.

    • @Entity: Properties are considered for persistence in sub classes, and the base class itself is a normally persisted entity.

    For example:

    The model for Sub, Sub_, will now include all properties: id , baseString and subString .

    It is also possible to inherit properties from another entity:

    Notes on usage

    • It is possible to have classes in the inheritance chain that are not annotated with @BaseEntity. Their properties will be ignored and will not become part of the entity model.

    • It is not generally recommend to have a base entity class consisting of an ID property only. E.g. Java imposes an additional overhead to construct objects with a sub class.

    • Depending on your use case using interfaces may be more straightforward.

    Restrictions

    • Superclasses annotated with @BaseEntity can not be part of a library.

    • There are no polymorphic queries (e.g. you cannot query for a base class and expect results from sub classes).

    • Currently any superclass, whether it is an @Entity or @BaseEntity, can not have any relations (like a ToOne or ToMany property).

    Kotlin Support

    ObjectBox fully supports Kotlin for Android. Learn what to look out for when using ObjectBox with Kotlin, how to use the built-in Kotlin extension functions.

    ObjectBox and Kotlin

    ObjectBox comes with full Kotlin support for Android. This allows entities to be modeled in Kotlin classes (regular and data classes). With Kotlin support you can build faster apps even faster.

    This page assumes that you have added ObjectBox to your project and that you are familiar with basic functionality. The Getting Started page will help you out if you are not. This page discusses additional capabilities for Kotlin only.

    Kotlin Entities

    ObjectBox supports regular and data classes for entities. However, @Id properties must be var (not val) because ObjectBox assigns the ID after putting a new entity. They also should be of non-null type Long with the special value of zero for marking entities as new.

    Can sealed classes be entities? Not directly. are abstract and can't be instantiated. But subclasses of a sealed class should work.

    To learn how to create entities, look at these pages:

    Defining Relations in Kotlin Entities

    When in Kotlin, keep in mind that relation properties must be var. Otherwise they can not be initialized as described in the . To avoid null checks use a lateinit modifier. When using a data class this requires the relation property to be moved to the body.

    For non-Android projects, i.e. if you are using Kotlin for desktop apps, there's an additional setup for for entities necessary, please see for details. In the future, we hope to eliminate this requirement.

    See the Relations page for examples.

    Two data classes that have the same property values (excluding those defined in the class body) . Keep this in mind when working with ToMany which uses a HashMap to keep track of changes. E.g. adding the same data class multiple times has no effect, it is treated as the same entity.

    Using the provided extension functions

    To simplify your code, you might want to use the Kotlin extension functions provided by ObjectBox. The library containing them is added automatically if the Gradle plugin detects a Kotlin project.

    To add it manually, modify the dependencies section in your app's build.gradle file:

    Now have a look at what is possible with the extensions compared to standard Kotlin idioms:

    Get a box:

    Queries

    The makes below extensions functions unnecessary.

    Build a query:

    Use the in filter of a query:

    Relations

    Modify a :

    Flow

    Get a Flow from a Box or Query subscription (behind the scenes this is based on a ):

    Something missing? what other extension functions you want us to add.

    Coroutines

    To run Box operations on a separate Dispatcher wrap them using withContext:

    BoxStore provides an async API to run transactions. There is an extension function available that wraps it in a coroutine:

    Next Steps

    • Check out the .

    • Continue with .

    Custom Types

    How to store types that are not built-in, recommendations for storing enums, and using property converters.

    ObjectBox supports a wide range of . For types that aren't directly supported, you can use converters to map them to a built-in type.

    Flex property converters

    In ObjectBox for Java/Kotlin, use converters internally. You can exchange converters to fine-tune the conversion process. To override the default converter, use @Convert. For example to use another built-in FlexObjectConverter subclass:

    FAQ

    Answers to questions specific to ObjectBox for Java and Dart

    Does ObjectBox support Kotlin? RxJava?

    ObjectBox comes with full including data classes. And yes, it supports .

    store.runInTransaction(TxMode.write, () {
      for (var user in allUsers) {
        if (modify(user)) {
          box.put(user);
        } else {
          box.remove(user.id);
        }
      }
    });
    with store.write_tx():
        for user in allUsers:
            if modify(user):
                box.put(user)
            else:
                box.remove(user)
    // DON'T DO THIS!
    for (User user : allUsers) {
        modify(user);
        box.put(user);
    }
    // DON'T DO THIS!
    for (user in allUsers) {
        modify(user)
        box.put(user)
    }
    // DON'T DO THIS!
    for (var user in allUsers) {
        modify(user);
        box.put(user);
    }
    for (User user : allUsers) {
        modify(user);
    }
    box.put(allUsers);
    for (user in allUsers) {
        modify(user)
    }
    box.put(allUsers)
    for (var user in allUsers) {
        modify(user);
    }
    box.putMany(allUsers);
    boxStore.runInTx(() -> {
        for (User user : allUsers) {
            if (modify(user)) {
                box.put(user);
            } else {
                box.remove(user);
            }
        }
    });
    in-depth documentation on UIDs and concepts
    // Superclass:
    @BaseEntity
    public abstract class Base {
        
        @Id long id;
        String baseString;
        
        public Base() {
        }
        
        public Base(long id, String baseString) {
            this.id = id;
            this.baseString = baseString;
        }
    }
    
    // Subclass:
    @Entity
    public class Sub extends Base {
        
        String subString;
        
        public Sub() {
        }
        
        public Sub(long id, String baseString, String subString) {
            super(id, baseString);
            this.subString = subString;
        }
    }
    Getting started
    Sealed classes
    Getting started
    Entity Annotations
    defining relations
    relations docs
    https://docs.objectbox.io/relations#initialization-magic
    Relations
    are equal and have the same hash code
    new Query API
    ToMany
    Data Observer
    Let us know
    Kotlin example on GitHub
    Getting Started
    Does ObjectBox support object relations?

    Yes. ObjectBox comes with strong relation support and offers features like “eager loading” for optimal performance.

    Does ObjectBox support multi-module projects? Can entities be spread across modules?

    The ObjectBox Gradle plugin only looks for entities in the current module, it does not search library modules. However, you can have a separate database (MyObjectBox file) for each module. Just make sure to pass different database names when building your BoxStore.

    Is ObjectBox a “zero copy” database? Are properties fetched lazily?

    It depends. Internally and in the C API, ObjectBox does zero-copy reads. Java objects require a single copy only. However, copying data is only a minor factor in overall performance. In ObjectBox, objects are POJOs (plain objects), and all properties will be properly initialized. Thus, there is no run time penalty for accessing properties and values do not change in unexpected ways when the database updates.

    Are there any threading constrictions?

    No. The objects you get from ObjectBox are POJOs (plain objects). You are safe to pass them around in threads.

    Should I use ObjectBox on the main thread (or UI thread)?

    It depends. In most cases no IO operations (which is what ObjectBox does) should be run on the main thread. This avoids (even rare) hangs of your app.

    However, in some cases it might be alright. While ObjectBox and the underlying OS and file system can give no hard guarantees, reading (e.g. Box.get(id)) small amounts of data is typically very fast and should have no notable impact on observed performance of your app. This is because in ObjectBox reads, unlike writes, are not blocked by other operations.

    On which platforms does ObjectBox run?

    ObjectBox supports Android 5.0 (API level 21) or newer and works on most device architectures (armeabi-v7a, arm64-v8a, x86 and x86_64). An Android library is available for Java (also Kotlin) and Flutter projects.

    ObjectBox supports iOS 12 or newer on 64-bit devices only. An iOS library is available for Flutter or Swift projects.

    ObjectBox also runs on Linux (x86_64, arm64, armv7), Windows (x86_64) and macOS 10.15 or newer (x86_64, Apple M1) with support for Kotlin, Java, Dart, Flutter, Go, C, Swift and Python.

    Can I use ObjectBox on the desktop/server?

    Yes, you can ObjectBox on the desktop/server side. Contact us for details if you are interested in running ObjectBox in client/server mode or containerized!

    Can I use ObjectBox on smart IoT devices?

    Yes. You can run the ObjectBox database on any IoT device that runs Linux. We also offer Go and C APIs. Check our cross-platform tutorial and see how easy it is to sync data across platforms in real time with ObjectBox.

    How do I rename object properties or classes?

    If you only do a rename on the language level, ObjectBox will by default remove the old and add a new entity/property. To do a rename, you must specify the UID.

    How much does ObjectBox add to my APK size?

    The Google Play download size increases by around 2.3 MB (checked for ObjectBox 3.0.0) as a native library for each supported architecture is packaged. If you build multiple APKs split by ABI or use Android App Bundle it only increases around 0.6 MB.

    Tip: Open your APK or AAB in Android Studio and have a look at the lib folder to see the raw file size and download size added.

    When building with minimum API level 23 (Android 6.0), the raw file (APK or AAB) size increases more, by around 6.1 MB. This is because the Android Plugin adds extractNativeLibs="false" to your AndroidManifest.xml as recommended by Google. This turns off compression. However, this allows Google Play to optimally compress APKs before downloading them to each device (see download size above) and reduces the size of your app updates (on Android 6.0 or newer). Read this Android developers post for details. It also avoids issues that might occur when extracting the libraries.

    If you rather have a smaller APK/App Bundle instead of smaller app downloads and updates (e.g. when distributing in other stores) you can override the flag in your AndroidManifest.xml:

    This is also currently recommended in any case when building a Flutter app with minimum API level 23 (Android 6.0).

    More importantly, ObjectBox adds little to the APK method count since it’s mostly written in native code.

    Can I ship my app with a pre-built database?

    Yes. ObjectBox stores all data in a single database file. Thus, you just need to prepare a database file and copy it to the correct location on the first start of your app (before you touch ObjectBox’s API).

    For Java, there is an experimental initialDbFile() method when building BoxStore. Let us know if this is useful!

    The database file is called data.mdb and is typically located in a subdirectory called objectbox (or any name you passed to BoxStoreBuilder). On Android, the DB file is located inside the app’s files directory inside objectbox/objectbox/. Or objectbox/<yourname> if you assigned the custom name <yourname> using BoxStoreBuilder.

    How does ObjectBox use disk space? And, can I reclaim disk space?

    In most cases, ObjectBox uses disk space quite optimally. Only once you add more data, the database file grows as required. When you delete data, file areas are marked as unused internally and will be reused by ObjectBox. Note that re-using existing file areas is much more efficient than shrinking and growing the file. In practice, once used file storage will be used again in the future; especially considering that stored data has the tendency to get more over time.

    ObjectBox relies on multi-version concurrency storage based on "copy on write". This allows e.g. to read the previous state while a write transaction is active. A counter-intuitive consequence is that deleting data can actually increase disk usage because the old data is still referenced. But of course, forthcoming transactions can reuse the internally reclaimed space.

    The storage layout on disk is optimized for performance. Database structures and concepts like B+ trees, multi-version concurrency and indexes use more space than storing data e.g. in a text file. Advantages like scalable data operations easily make up for it. Also keep in mind that in many cases data stored in a database is a small proportion compared to media files.

    Non-standard use cases may require a temporary peak in data storage space that is followed by a permanent drop of storage space. To reclaim disk space for those cases, you need to delete the database files and restore them later; e.g. from the cloud or from a second store, which you set up to put the objects you want to keep.

    Deleting the database files deletes the contained data permanently. If you want to restore old data, it's your responsibility to backup and restore.

    While we don't recommend deleting the entire database, the API offers some methods to do so: first, close() the BoxStore and then delete the database files using BoxStore.deleteAllFiles(objectBoxDirectory). To avoid having to close BoxStore delete files before building it, e.g. during app start-up.

    BoxStore.removeAllObjects() does not reclaim disk space. It keeps the allocated disk space so it returns fast and to avoid the performance hit of having to allocate the same disk space when data is put again.

    Answers to other questions

    Questions that apply to all supported platforms and languages are answered in the general ObjectBox FAQ.

    If you believe to have found a bug or missing feature, please create an issue.

    For Java/Kotlin: https://github.com/objectbox/objectbox-java/issues

    For Flutter/Dart: https://github.com/objectbox/objectbox-dart/issues

    If you have a usage question regarding ObjectBox, please post on Stack Overflow. https://stackoverflow.com/questions/tagged/objectbox

    Kotlin support
    RxJava and reactive queries without RxJava
    Troubleshooting
    boxStore.runInTx {
        for (user in allUsers) {
            if (modify(user)) {
                box.put(user)
            } else {
                box.remove(user)
            }
        }
    }
    @Entity
    @Uid
    public class MyName { ... }
    @Entity(uid: 0)
    class MyName { ... }
    error: [ObjectBox] UID operations for entity "MyName": 
      [Rename] apply the current UID using @Uid(6645479796472661392L) -
      [Change/reset] apply a new UID using @Uid(4385203238808477712L)
    @Entity(uid: 0) found on "MyName" - you can choose one of the following actions:
        [Rename] apply the current UID using @Entity(uid: 6645479796472661392)
        [Change/reset] apply a new UID using @Entity(uid: 4385203238808477712)
    @Entity
    @Uid(6645479796472661392L)
    public class MyName { ... }
    @Entity(uid: 6645479796472661392)
    class MyName { ... }
    @Entity
    @Uid(6645479796472661392L)
    public class MyNewName { ... }
    @Entity(uid: 6645479796472661392)
    class MyNewName { ... }
    @Entity()
    class MyName:
        id = Id
        some_property = Int
    print(MyName._uid)
    # 6645479796472661392
    @Entity(uid=6645479796472661392)
    class MyNewName:
        id = Id
        some_property = Int
    // old:
    String year;
    // new:
    int yearInt;
    @Uid
    String year;
    @Property(uid: 0)
    String year;
    error: [ObjectBox] UID operations for property "MyEntity.year": 
      [Rename] apply the current UID using @Uid(6707341922395832766L) -
      [Change/reset] apply a new UID using @Uid(9204131405652381067L)
    @Property(uid: 0) found on "year" - you can choose one of the following actions:
        [Rename] apply the current UID using @Property(uid: 6707341922395832766)
        [Change/reset] apply a new UID using @Property(uid: 9204131405652381067)
    @Uid(9204131405652381067L)
    int year;
    @Property(uid: 9204131405652381067)
    String year;
    // Note: Kotlin data classes do not support inheritance,
    // so this example uses regular Kotlin classes.
    
    // Superclass:
    @BaseEntity
    abstract class Base {
        @Id
        var id: Long = 0
        var baseString: String? = null
    
        constructor()
        constructor(id: Long, baseString: String?) {
            this.id = id
            this.baseString = baseString
        }
    }
    
    // Subclass:
    @Entity
    class Sub : Base {
        var subString: String? = null
    
        constructor()
        constructor(id: Long, 
                    baseString: String?,
                    subString: String?) : super(id, baseString) {
            this.subString = subString
        }
    }
    // Entities inherit properties from super entities.
    @Entity
    public class SubSub extends Sub {
        
        String subSubString;
        
        public SubSub() {
        }
        
        public SubSub(long id, String baseString,
                      String subString, String subSubString) {
            super(id, baseString, subString);
            this.subSubString = subSubString;
        }
    }
    // Entities inherit properties from super entities.
    @Entity
    class SubSub : Sub {
        var subSubString: String? = null
    
        constructor()
        constructor(id: Long,
                    baseString: String?,
                    subString: String?,
                    subSubString: String?) : super(id, baseString, subString) {
            this.subSubString= subSubString
        }
    }
    // THIS DOES NOT WORK
    @BaseEntity
    public abstract class Base {
        @Id long id;
        ToOne<OtherEntity> other; 
        ToMany<OtherEntity> others; 
    }
    // THIS DOES NOT WORK
    @BaseEntity
    abstract class Base {
        @Id
        var id: Long = 0
        lateinit var other: ToOne<OtherEntity>
        lateinit var others: ToMany<OtherEntity>
    }
    dependencies {
        implementation("io.objectbox:objectbox-kotlin:$objectboxVersion")
    }
    // Regular:
    val box = store.boxFor(DataClassEntity::class.java)
    
    // With extension:
    val box: Box<DataClassEntity> = store.boxFor()
    // Regular:
    val query = box.query().run {
        equal(property, value)
        order(property)
        build()
    }
    
    // With extension:
    val query = box.query {
        equal(property, value)
        order(property)
    }
    // Regular:
    val query = box.query().`in`(property, array).build()
    
    // With extension:
    val query = box.query().inValues(property, array).build()
    // Regular:
    toMany.apply { 
        reset()
        add(entity)
        removeById(id)
        applyChangesToDb()
    }
    
    // With extension:
    toMany.applyChangesToDb(resetFirst = true) { // default is false
        add(entity)
        removeById(id)
    }
    // Listen to all changes to a Box
    val flow = store.subscribe(TestEntity::class.java).toFlow()
    // Get the latest query results on any changes to a Box
    val flow = box.query().subscribe().toFlow()
    suspend fun putNote(
        note: Note, 
        dispatcher: CoroutineDispatcher
    ) = withContext(dispatcher) {
        boxStore.boxFor(Note::class.java).put(note)
    }
    // Calls callInTxAsync behind the scenes.
    val id = boxStore.awaitCallInTx {
        box.put(Note("Hello", 1))
    }
    <application
        ...
        // not recommended for non-Flutter apps, increases app update size
        android:extractNativeLibs="true"
        tools:replace="android:extractNativeLibs"
        ...   
    </applicaton>
    // If BoxStore is in use, close it first.
    store.close();
    
    BoxStore.deleteAllFiles(new File(BoxStoreBuilder.DEFAULT_NAME));
    
    // TODO Build a new BoxStore instance.
    // StringLongMapConverter restores any integers always as Long
    @Convert(converter = StringLongMapConverter::class, dbType = ByteArray::class)
    var stringMap: MutableMap<String, Any>? = null

    You can also write a custom converter like shown below.

    Convert annotation and property converter

    To add support for a custom type, you need to provide a conversion to one of the ObjectBox built-in types. For example, you could define a color in your entity using a custom Color class and map it to an Integer. Or you can map the popular org.joda.time.DateTime from Joda Time to a Long.

    Here is an example mapping an enum to an integer:

    @Entity
    public class User {
        @Id
        public long id;
        
        @Convert(converter = RoleConverter.class, dbType = Integer.class
    

    Things to look out for

    If you define your custom type or converter inside a Java or Kotlin entity class, it must be static or respectively not an inner class.

    Don’t forget to handle null values correctly – usually, you should return null if the input is null.

    Database types in the sense of the converter are the primitive (built-in) types offered by ObjectBox, as mentioned in the beginning. It is recommended to use a primitive type that is easily convertible (int, long, byte array, String, …).

    You must not interact with the database (such as using Box or BoxStore) inside the converter. The converter methods are called within a transaction, so for example, getting or putting entities to a box will fail.

    Note: For optimal performance, ObjectBox will use a single converter instance for all conversions. Make sure the converter does not have any other constructor besides the parameter-less default constructor. Also, make it thread-safe, because it might be called concurrently on multiple entities.

    List/Array types

    You can use a converter with List types. For example, you could convert a List of Strings to a JSON array resulting in a single string for the database. At the moment it is not possible to use an array with converters (you can track this feature request).

    ObjectBox has built-in support for many list and array types, including String lists/arrays and typed numeric arrays.

    How to convert Enums correctly

    Enums are popular with data objects like entities. When persisting enums, there are a couple of best practices:

    • Do not persist the enum’s ordinal or name: Both are unstable, and can easily change the next time you edit your enum definitions.

    • Use stable ids: Define a custom property (integer or string) in your enum that is guaranteed to be stable. Use this for your persistence mapping.

    • Prepare for the unknown: Define an UNKNOWN enum value. It can serve to handle null or unknown values. This will allow you to handle cases like an old enum value getting removed without crashing your app.

    Custom types in queries

    QueryBuilder is unaware of custom types. You have to use the primitive DB type for queries.

    So for the Role example above you would get users with the role of admin with the query condition .equal(UserProperties.Role, 2).

    built-in property types
    Flex properties
    // StringLongMapConverter restores any integers always as Long
    @Convert(converter = StringLongMapConverter.class, dbType = byte[].class)
    @Nullable Map<String, Object> stringMap;

    Property Types

    ObjectBox supports a variety of data types inside the stored objects including various list types and even special "flex" types for dynamic and schema-less data.

    ObjectBox stores data as objects, which are instances of entity classes you define. Objects consist of properties, which are the individual fields that hold your data, like a user's name or age. This page covers the property types ObjectBox supports:

    • Standard types for single values of a fixed type

    • List/Vector types for collections of values of a fixed type

    • The flex type for dynamic and schema-less data

    Standard Types

    Standard types include boolean, integers, floating point types, string, and date types. These are the most common types you will use in typical applications.

    Quick Reference

    Category
    ObjectBox Type
    Stored as

    Nullable types: All ObjectBox property types support null values. In Java, use wrapper classes (Integer vs int) for nullable types. In Kotlin, Dart, and Python, nullability is part of the type system.

    Integers

    ObjectBox supports all standard integer types, from 8-bit to 64-bit. Rule of thumb: use large enough integer types that are safe for your use case in the future. Use smaller types only if you are certain about the range of values as you cannot change the type of an existing property later.

    Kotlin unsigned types (UByte, UShort, UInt, ULong) are also supported—they're stored as their signed equivalents.

    Note: Dart just has int and does not differentiate between the integer types. You can still use ObjectBox types to pick the storage type and align with other languages (e.g. when using Sync).

    Floating Point

    Choose 32-bit floats for memory efficiency when ~7 digits of precision is enough. Use 64-bit doubles when you need ~15 digits.

    Strings

    Strings are stored as UTF-8 encoded bytes without a length limit.

    Dates and Times

    ObjectBox stores dates as 64-bit integers ("timestamps") representing time since the Unix epoch (January 1, 1970, 00:00:00 UTC). Choose your precision:

    Precision
    ObjectBox Type
    Use case

    Note: while DateNano values are always stored as nanoseconds, the actual precision of the values depends on the platform and programming language. For example, Dart uses only microsecond precision (the last three digits of the nanosecond are "truncated").

    Proper UTC handling requires ObjectBox for Dart/Flutter 5.1 with the dateUtc and dateNanoUtc annotations. Prior versions do not support proper UTC handling. Please update and avoid the old date and dateNano annotations.

    IDs and Relations

    Every ObjectBox entity must have exactly one ID property of type Long (64-bit integer). See for details.

    "Relation" is used for one of two relation types available in ObjectBox. It's a 64-bit integer that references the ID of another object. Depending on the programming language you use, it may translate to a "to-one" construct. See for complete documentation.

    Lists and Arrays

    Store collections of values directly; no special tables or joins required. The internal type names are "vectors", but they may translate to lists or arrays in the programming language you use.

    Binary data is stored as a ByteVector. In other databases, this is may be called a "BLOB" (Binary Large Object).

    Quick Reference

    ObjectBox Type
    Description

    Language Examples

    For vector search: Float vectors used for embeddings and similarity search have special indexing support. See .

    Flex Properties

    Flex properties can hold data values of various types, including complex structures, in a single property. They serve two main purposes:

    • Dynamic data: you want to process JSON-like data dynamically

    • Nested objects: as an alternative to relations, you can embed map-like data directly

    When syncing with MongoDB, flex properties can hold nested documents.

    See for Java/Kotlin additional notes.

    See for Java/Kotlin additional notes.

    Custom Types

    If the existing property types do not fully cover your use case, check .

    Advanced Setup

    Additional configuration options when creating an ObjectBox database.

    This page contains:

    ObjectBox for Java - Advanced Setup

    ObjectBox for Flutter/Dart - Advanced Setup

    ObjectBox for Java - Advanced Setup

    This page assumes you have added ObjectBox to your project.

    To then change the default behavior of the ObjectBox plugin and processor read on for advanced setup options.

    Manually Add Libraries

    The ObjectBox Gradle plugin adds required libraries and the annotation processor to your projects dependencies automatically, but you can also add them manually.

    Just make sure to apply the ObjectBox Gradle plugin after the dependencies block, so it does not replace manually added dependencies.

    In your app's Gradle build script:

    Add libraries for distribution

    For JVM apps, by default, the ObjectBox Gradle plugin only adds the native (Linux, macOS or Windows) library required to run on your current system. If your app wants to support multiple platforms, manually add all of the required native libraries listed above when you distribute your app.

    Processor Options

    In your app’s Gradle build script, the following processor options, explained below, are available:

    Change the Model File Path

    By default, the ObjectBox model file is stored in module-name/objectbox-models/default.json. You can change the file path and name by passing the objectbox.modelPath argument to the ObjectBox annotation processor.

    Change the MyObjectBox package

    Since 1.5.0

    By default, the MyObjectBox class is generated in the same or a parent package of your entity classes. You can define a specific package by passing the objectbox.myObjectBoxPackage argument to the ObjectBox annotation processor.

    Enable Debug Mode

    You can enable debug output for the annotation processor if you encounter issues while setting up your project and entity classes.

    In your app’s build.gradle file, enable the objectbox.debug option and then run Gradle with the --info option to see the debug output.

    To enable debug mode for the ObjectBox Gradle plugin:

    Enable DaoCompat mode

    ObjectBox can help you migrate from greenDAO by generating classes with a greenDAO-like API.

    See the on how to enable and use this feature.

    ObjectBox for Flutter/Dart - Advanced Setup

    Change the generated files directory

    To customize the directory (relative to the package root) where the files generated by ObjectBox are written, add the following to your pubspec.yaml:

    Tutorial: Demo Project

    Learn how to build a simple note-taking app with ObjectBox.

    This tutorial will walk you through a simple note-taking app explaining how to do basic operations with ObjectBox. To just integrate ObjectBox into your project, look at the page.

    You can check out the example code from GitHub. This allows you to run the code and explore it in its entirety. It is a simple app for taking notes where you can add new notes by typing in some text and delete notes by clicking on an existing note.

    @Entity
    data class User(
            @Id
            var id: Long = 0,
            @Convert(converter = RoleConverter::class, dbType = Int::class)
            var role: Role? = null
    )
    
    enum class Role(val id: Int) {
        DEFAULT(0), AUTHOR(1), ADMIN(2);
    }
    
    class RoleConverter : PropertyConverter<Role?, Int?> {
        override fun convertToEntityProperty(databaseValue: Int?): Role? {
            if (databaseValue == null) {
                return null
            }
            for (role in Role.values()) {
                if (role.id == databaseValue) {
                    return role
                }
            }
            return Role.DEFAULT
        }
    
        override fun convertToDatabaseValue(entityProperty: Role?): Int? {
            return entityProperty?.id
        }
    }
    enum Role {
      unknown,
      author,
      admin
    }
    
    @Entity()
    class User {
      int id;
    
      // The Role type is not supported by ObjectBox.
      // So ignore this field...
      @Transient()
      Role? role;
    
      // ...and define a field with a supported type,
      // that is backed by the role field.
      int? get dbRole {
        _ensureStableEnumValues();
        return role?.index;
      }
    
      set dbRole(int? value) {
        _ensureStableEnumValues();
        if (value == null) {
          role = null;
        } else {
          role = Role.values[value]; // throws a RangeError if not found
    
          // or if you want to handle unknown values gracefully:
          role = value >= 0 && value < Role.values.length
              ? Role.values[value]
              : Role.unknown;
        }
      }
    
      User(this.id);
    
      void _ensureStableEnumValues() {
        assert(Role.unknown.index == 0);
        assert(Role.author.index == 1);
        assert(Role.admin.index == 2);
      }
    }
    )
    public Role role;
    public enum Role {
    DEFAULT(0), AUTHOR(1), ADMIN(2);
    final int id;
    Role(int id) {
    this.id = id;
    }
    }
    public static class RoleConverter implements PropertyConverter<Role, Integer> {
    @Override
    public Role convertToEntityProperty(Integer databaseValue) {
    if (databaseValue == null) {
    return null;
    }
    for (Role role : Role.values()) {
    if (role.id == databaseValue) {
    return role;
    }
    }
    return Role.DEFAULT;
    }
    @Override
    public Integer convertToDatabaseValue(Role entityProperty) {
    return entityProperty == null ? null : entityProperty.id;
    }
    }
    }

    Integer (64-bit)

    Long

    8 bytes

    To-One Relation

    Relation

    8 bytes

    Float (32-bit)

    Float

    4 bytes

    Float (64-bit)

    Double

    8 bytes

    Text Character

    Char

    2 bytes

    String

    String

    UTF-8 bytes

    Date (ms)

    Date

    8 bytes (long)

    Date (ns)

    DateNano

    8 bytes (long)

    FloatVector

    Float values (32-bit floating point)

    DoubleVector

    Double values (64-bit floating point)

    StringVector

    String values (UTF-8 encoded strings)

    DateVector

    Date values (64-bit timestamp)

    DateNanoVector

    DateNano values (high precision 64-bit timestamp)

    Boolean

    Bool

    1 byte

    Integer (8-bit)

    Byte

    1 byte

    Integer (16-bit)

    Short

    2 bytes

    Integer (32-bit)

    Int

    Milliseconds

    Date (the default)

    Most applications

    Nanoseconds

    DateNano

    High-precision timestamps

    BoolVector

    Bool values (stored using one byte per value)

    ByteVector

    Byte values (8-bit integers), binary data, BLOB

    ShortVector

    Short values (16-bit integers)

    CharVector

    Char values (16-bit characters)

    IntVector

    Int values (32-bit integers)

    LongVector

    Long values (64-bit integers)

    Entity Annotations
    Relations
    On-Device Vector Search
    FlexObjectConverter
    FlexObjectConverter
    custom types and converters

    4 bytes

    DaoCompat documentation
    dependencies {
        // All below added automatically by the plugin:
        // Java library
        implementation("io.objectbox:objectbox-java:$objectboxVersion")
        // Kotlin extension functions
        implementation("io.objectbox:objectbox-kotlin:$objectboxVersion")
        // Annotation processor
        kapt("io.objectbox:objectbox-processor:$objectboxVersion")
        // Native library for Android
        implementation("io.objectbox:objectbox-android:$objectboxVersion")
    }
    
    // Apply plugin after dependencies block so they are not overwritten.
    apply plugin: 'io.objectbox'
    // Or using Kotlin DSL:
    apply(plugin = "io.objectbox")
    dependencies {
        // All below added automatically by the plugin:
        // Java library
        implementation("io.objectbox:objectbox-java:$objectboxVersion")
        // Annotation processor
        annotationProcessor("io.objectbox:objectbox-processor:$objectboxVersion")
        // Native library for Android
        implementation("io.objectbox:objectbox-android:$objectboxVersion")
    }
    
    // Apply plugin after dependencies block so they are not overwritten.
    apply plugin: 'io.objectbox'
    // Or using Kotlin DSL:
    apply(plugin = "io.objectbox")
    @Entity
    public class Sensor {
        @Id public long id;
        
        public byte status;        // -128 to 127
        public short temperature;  // -32,768 to 32,767  
        public int count;          // ~2 billion range
        public long timestamp;     // Large numbers, IDs
    }
    @Entity
    data class Sensor(
        @Id var id: Long = 0,
        var status: Byte = 0,
        var temperature: Short = 0,
        var count: Int = 0,
        var timestamp: Long = 0
    )
    @Entity()
    class Sensor {
      @Id()
      int id = 0;
      
      @Property(type: PropertyType.byte)
      int status = 0;  // 8-bit
      
      @Property(type: PropertyType.short)
      int temperature = 0;  // 16-bit
      
      @Property(type: PropertyType.int)
      int count = 0;  // 32-bit
      
      int timestamp = 0;  // 64-bit (default)
    }
    @Entity()
    class Sensor:
        id = Id
        status = Int8
        temperature = Int16
        count = Int32
        timestamp = Int64
    public float score;      // 32-bit, ~7 significant digits
    public double precise;   // 64-bit, ~15 significant digits
    @Property(type: PropertyType.float)
    double score = 0;  // stored as 32-bit
    
    double precise = 0;  // 64-bit (default)
    public String name;
    public String description;  // Can be very long
    // Millisecond precision (default)
    public Date createdAt;
    
    // Nanosecond precision
    @Type(DatabaseType.DateNano)
    public long preciseTimestamp;
    // Millisecond precision (default)
    @Property(type: PropertyType.dateUtc)
    DateTime? createdAt;
    
    // Stored with nanosecond precision; however,
    // Dart uses only microsecond precision! 
    @Property(type: PropertyType.dateNanoUtc)
    DateTime? preciseTimestamp;
    // Primitive arrays
    public byte[] imageData;
    public int[] scores;
    public double[] measurements;
    
    // String collections
    public String[] tags;
    public List<String> categories;
    var imageData: ByteArray? = null
    var scores: IntArray? = null
    var measurements: DoubleArray? = null
    
    var tags: Array<String>? = null
    var categories: MutableList<String>? = null
    // Typed lists for efficiency
    Uint8List? imageData;
    Int32List? scores;
    Float64List? measurements;
    
    // String list
    List<String>? tags;
    image_data = Int8Vector
    scores = Int32Vector
    measurements = Float64Vector
    @Entity
    public class Customer {
        @Id long id;
    
        // Stores any supported type at runtime
        @Nullable Object tag;
    
        // Or explicitly use a String map
        @Nullable Map<String, Object> stringMap;
    
        // Or a list
        @Nullable List<Object> flexList;
        
        public Customer(Object tag) {
            this.id = 0;
            this.tag = tag;
        }
        
        public Customer() {} // For ObjectBox
    }
    
    Customer customerStrTag = new Customer("string-tag");
    Customer customerIntTag = new Customer(1234);
    box.put(customerStrTag, customerIntTag);
    @Entity
    data class Customer(
        @Id var id: Long = 0,
    
        // Stores any supported type at runtime
        var tag: Any? = null,
    
        // Or explicitly use a String map
        var stringMap: MutableMap<String, Any?>? = null
    
        // Or a list
        var flexList: MutableList<Any?>? = null
    )
    
    val customerStrTag = Customer(tag = "string-tag")
    val customerIntTag = Customer(tag = 1234)
    box.put(customerStrTag, customerIntTag)
    @Entity()
    class Customer {
      @Id()
      int id = 0;
      
      // Stores any supported type at runtime
      dynamic tag;
      
      // Or explicitly use a String map (JSON-like)
      Map<String, dynamic>? stringMap;
      
      // Or a list with mixed types
      List<dynamic>? flexList;
      
      // Or a list of maps
      List<Map<String, dynamic>>? nestedList;
    }
    
    final customerStrTag = Customer()..tag = "string-tag";
    final customerIntTag = Customer()..tag = 1234;
    box.putMany([customerStrTag, customerIntTag]);
    dependencies {
        // All below added automatically by the plugin:
        // Java library
        implementation("io.objectbox:objectbox-java:$objectboxVersion")
        // Annotation processor
        annotationProcessor("io.objectbox:objectbox-processor:$objectboxVersion")
        // One of the native libraries required for your system
        implementation("io.objectbox:objectbox-linux:$objectboxVersion")
        implementation("io.objectbox:objectbox-macos:$objectboxVersion")
        implementation("io.objectbox:objectbox-windows:$objectboxVersion")
        // Not added automatically:
        // Since 2.9.0 we also provide ARM support for the Linux library
        implementation("io.objectbox:objectbox-linux-arm64:$objectboxVersion")       
        implementation("io.objectbox:objectbox-linux-armv7:$objectboxVersion")
    }
    
    // Apply plugin after dependencies block so they are not overwritten.
    apply plugin: "io.objectbox"
    // Or using Kotlin DSL:
    apply(plugin = "io.objectbox")
    dependencies {
        // All below added automatically by the plugin:
        // Java library
        implementation("io.objectbox:objectbox-java:$objectboxVersion")
        // Kotlin extension functions
        implementation("io.objectbox:objectbox-kotlin:$objectboxVersion")
        // Annotation processor
        kapt("io.objectbox:objectbox-processor:$objectboxVersion")
        // One of the native libraries required for your system
        implementation("io.objectbox:objectbox-linux:$objectboxVersion")
        implementation("io.objectbox:objectbox-macos:$objectboxVersion")
        implementation("io.objectbox:objectbox-windows:$objectboxVersion")
        // Not added automatically:
        // Since 2.9.0 we also provide ARM support for the Linux library
        implementation("io.objectbox:objectbox-linux-arm64:$objectboxVersion")       
        implementation("io.objectbox:objectbox-linux-armv7:$objectboxVersion")
    }
    
    // Apply plugin after dependencies block so they are not overwritten.
    apply plugin: "io.objectbox"
    // Or using Kotlin DSL:
    apply(plugin = "io.objectbox")
    kapt {
        arguments {
            arg("objectbox.modelPath", "$projectDir/schemas/objectbox.json")
            arg("objectbox.myObjectBoxPackage", "com.example.custom")
            arg("objectbox.debug", true)
        }
    }
    // Groovy DSL (build.gradle)
    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [ 
                            "objectbox.modelPath" : "$projectDir/schemas/objectbox.json".toString(),
                            "objectbox.myObjectBoxPackage" : "com.example.custom",
                            "objectbox.debug" : "true"
                    ]
                }
            }
        }
    }
    
    // Kotlin DSL (build.gradle.kts)
    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments.put("objectbox.modelPath", "$projectDir/schemas/objectbox.json")
                    arguments.put("objectbox.myObjectBoxPackage", "com.example.custom")
                    arguments.put("objectbox.debug", "true")
                }
            }
        }
    }
    // Groovy DSL
    tasks.withType(JavaCompile) {
        options.compilerArgs += [ "-Aobjectbox.modelPath=$projectDir/schemas/objectbox.json" ]
        options.compilerArgs += [ "-Aobjectbox.myObjectBoxPackage=com.example.custom" ]
        options.compilerArgs += [ "-Aobjectbox.debug=true" ]
    }
    
    // Kotlin DSL
    tasks.withType<JavaCompile>() {
        options.compilerArgs.add("-Aobjectbox.modelPath=$projectDir/schemas/objectbox.json")
        options.compilerArgs.add("-Aobjectbox.myObjectBoxPackage=com.example.custom")
        options.compilerArgs.add("-Aobjectbox.debug=true")
    }
    kapt {
        arguments {
            arg("objectbox.modelPath", "$projectDir/schemas/objectbox.json")
            arg("objectbox.myObjectBoxPackage", "com.example.custom")
            arg("objectbox.debug", true)
        }
    }
    // Enable debug output for the plugin
    // Groovy DSL
    objectbox {
        debug = true
    }
    
    // Kotlin DSL
    configure<io.objectbox.gradle.ObjectBoxPluginExtension> {
        debug.set(true)
    }
    objectbox:
      # Writes objectbox-model.json and objectbox.g.dart to lib/custom (and test/custom).
      output_dir: custom
      # Or optionally specify the lib and test output folder separately.
      # output_dir:
      #   lib: custom
      #   test: other
    The Note entity and Box class

    To store notes there is an entity class called Note (or Task in Python). It defines the structure or model of the data persisted (saved) in the database for a note: its id, the note text and the creation date.

    src/Note.java
    @Entity
    public class Note {
        
        @Id
        long id;
        
        String text;
        String comment;
    
    src/Note.kt
    @Entity
    data class Note(
            @Id var id: Long = 0,
            var text: String? = null,
    
    lib/model.dart
    @Entity()
    class Note {
      int id;
      String text;
      String? comment;
      DateTime date;
    
      ...
    

    In general, an ObjectBox entity is an annotated class persisted in the database with its properties. In order to extend the note or to create new entities, you simply modify or create new plain classes and annotate them with @Entity and @Id; in Python opt-in uid argument i.e. @Entity(uid=).

    Go ahead and build the project, for example by using Build > Make project in Android Studio. This triggers ObjectBox to generate some classes, like MyObjectBox.java, and some other classes used by ObjectBox internally.

    Go ahead and build the project, for example by using Build > Make project in Android Studio. This triggers ObjectBox to generate some classes, like MyObjectBox.kt, and some other classes used by ObjectBox internally.

    Before running the app, run the ObjectBox code generator to create binding code for the entity classes: flutter pub run build_runner build

    Also re-run this after changing the note class.

    Inserting notes

    To see how new notes are added to the database, take a look at the following code fragments. The Box provides database operations for Note objects. A Box is the main interaction with object data.

    Note: In the example project, ObjectBox is the name of a helper class to set up and keep a reference to BoxStore.

    Note: In the example project, ObjectBox is the name of a helper class to set up and keep a reference to BoxStore.

    When a user adds a note the method addNote() is called. There, a new Note object is created and put into the database using the Box reference:

    Note that the ID property (0 when creating the Note object), is assigned by ObjectBox during a put.

    Removing/deleting notes

    When the user taps a note, it is deleted. The Box provides remove() to achieve this:

    Querying notes

    To query and display notes in a list a Query instance is built once:

    And then executed each time any notes change:

    You can also use self._task_box.get_all() to get all Task objects without any condition (instead of building a query).

    In addition to a result sort order, you can add various conditions to filter the results, like equality or less/greater than, when building a query.

    Updating notes and more

    What is not shown in the example, is how to update an existing (== the ID is not 0) note. Do so by just modifying any of its properties and then put it again with the changed object:

    There are additional methods to put, find, query, count or remove entities. Check out the methods of the Box class in API docs (for Java/Kotlin or Dart) to learn more.

    Setting up the database

    Now that you saw ObjectBox in action, how did we get that database (or store) instance? Typically you should set up a BoxStore or Store once for the whole app. This example uses a helper class as recommended in the Getting Started guide.

    Remember: ObjectBox is a NoSQL database on its own and thus NOT based on SQL or SQLite. That’s why you do not need to set up “CREATE TABLE” statements during initialization.

    Note: it is perfectly fine to never close the database. That’s even recommended for most apps.

    Getting Started
    git clone https://github.com/objectbox/objectbox-examples.git
    cd objectbox-examples/android-app
    git clone https://github.com/objectbox/objectbox-examples.git
    cd objectbox-examples/android-app-kotlin
    git clone https://github.com/objectbox/objectbox-dart.git
    cd objectbox-dart/objectbox/examples/flutter/objectbox_demo
    git clone https://github.com/objectbox/objectbox-python.git
    cd objectbox-python
    Entity Annotations
    ObjectBox Queries
    Getting started
    Getting started

    Meta Model, IDs, and UIDs

    Explaining the ObjectBox meta model file and how to resolve model file conflicts.

    Unlike relational databases like SQLite, ObjectBox does not require you to create a database schema. That does not mean ObjectBox is schema-less. For efficiency reasons, ObjectBox manages a meta model of the data stored. This meta model is actually ObjectBox’s equivalent of a schema. It includes known object types including all properties, indexes, etc. A key difference to relational schemas is that ObjectBox tries to manage its meta model automatically. In some cases it needs your help. That’s why we will look at some details.

    IDs

    In the ObjectBox meta model, everything has an ID and a UID. IDs are used internally in ObjectBox to reference entities, properties, and indexes. For example, you have an entity “User” with the properties “id” and “name”. In the meta model the entity (type) could have the ID 42, and the properties the IDs 1 and 2. Property IDs must only be unique within their entity.

    Note: do not confuse object IDs with meta model IDs: object IDs are the values of the @Id property (see Object IDs in ). In contrast, all objects are instances of the entity type associated with a single meta model ID.

    ObjectBox assigns meta model IDs sequentially (1, 2, 3, 4, …) and keeps track of the last used ID to prevent ID collisions.

    UIDs

    As a rule of thumb, for each meta model ID there’s a corresponding UID. They complement IDs and are often used in combination (e.g. in the JSON file). While IDs are assigned sequentially, UIDs are a random long value. The job of UIDs is detecting and resolving concurrent modifications of the meta model.

    A UID is unique across entities, properties, indexes, etc. Thus unlike IDs, an UID already used for an entity may not be used for a property. As a precaution to avoid side effects, ObjectBox keeps track of “retired” UIDs to ensure previously used but now abandoned UIDs are not used for new artifacts.

    JSON for consistent IDs

    ObjectBox stores a part of its meta model in a JSON file. This file should be available to every developer and thus checked into a source version control system (e.g. git). The main purpose of this JSON file is to ensure consistent IDs and UIDs in the meta model across devices.

    This JSON file is stored in

    • objectbox-models/default.json for Android or Java projects,

    • lib/objectbox-model.json for Dart or Flutter projects.

    For example, look at a file from the :

    As you can see, the “id” attributes combine the ID and UID using a colon. This protects against faulty merges. When applying the meta model to the database, ObjectBox will check for consistent IDs and UIDs.

    Meta Model Synchronization

    At build time, ObjectBox gathers meta model information from the entities (@Entity classes) and the JSON file. The complete meta model information is written into the generated class MyObjectBox.

    Then, at runtime, the meta model assembled in MyObjectBox is synchronized with the meta model inside the ObjectBox database (file). UIDs are the primary keys to synchronize the meta model with the database. The synchronization involves a couple of consistency checks that may fail when you try to apply illegal meta data.

    Stable Renames using UIDs

    At some point you may want to rename an entity class or just a property. Without further information, ObjectBox will remove the old entity/property and add a new one with the new name. This is actually a valid scenario by itself: removing one property and adding another. To tell ObjectBox it should do a rename instead, you need to supply the property's previous UID.

    Add an @Uid annotation without any value to the entity or property you want to rename and trigger a project build. The build will fail with a message containing the UID you need to apply to the @Uid annotation.

    Also check out this for hands-on information on renaming and resetting.

    Resolving Meta Model Conflicts

    In the section on UIDs, we already hinted at the possibility of meta model conflicts. This means the meta model defined in the objectbox-models/default.json file conflicts with the model of the existing database file.

    When creating a Store the meta model is passed and verified against the database file. If a conflict exists an exception or error is thrown, for example:

    Such a conflict can be caused by

    • developers changing the meta model at the same time (e.g. in different version control system branches), typically by adding entity classes or properties.

    • the objectbox-models/default.json model file getting accidentally deleted, it is then generated on the next build with a new set of UIDs.

    • a database file with a different model getting restored by or getting .

    There are basically two ways to resolve this:

    • Resolve conflicts in or reconstruct the default.json meta model file to match the model of an existing database file.

    • Keep or create a fresh default.json model file and delete the database file. However, this will lose all existing data.

    The two options are explained in detail below.

    Manual conflict resolution

    Usually, it is preferred to edit the JSON file to resolve conflicts and fix the meta model. This involves the following steps:

    • Ensure IDs are unique: in the JSON file the id attribute has values in the format “ID:UID”. If you have duplicate IDs after a VCS merge, you should assign a new ID (keep the UID part!) to one of the two. Typically, the new ID would be “last used ID + 1”.

    • Update last ID values: for entities, update the attribute lastEntityId; for properties, update the attribute lastPropertyId of the enclosing entity back to the ID:UID with the ID indicated by the error message.

    • Check for other ID references:

    To illustrate this with an example, let's assume the last assigned entity ID was 41. Thus the next entity ID will be 42. Now, the developers Alice and Bob add a new entity without knowing of each other. Alice adds a new entity “Ant” which is assigned the entity ID 42. At the same time, Bob adds the entity “Bear” which is also assigned the ID 42. After both developers committed their code, the ID 42 does not unique identify an entity type (“Ant” or “Bear”?). Furthermore, in Alice’s ObjectBox the entity ID 42 is already wired to “Ant” while Bob’s ObjectBox maps 42 to “Bear”. UIDs make this situation resolvable. Let’s say the UID is 12345 for “Ant” and 9876 for “Bear”. Now, when Bob pulls Alice’s changes, he is able to resolve the conflict. He manually assigns the entity ID 43 to “Bear” and updates the lastEntityId attribute accordingly to “43:9876” (ID:UID). After Bob commits his changes, both developers are able to continue with their ObjectBox files.

    The Nuke Option

    During initial development, it may be an option to just delete the meta model and all databases. This will cause a fresh start for the meta model, e.g. all UIDs will be regenerated. Follow these steps:

    • Delete the JSON file (objectbox-models/default.json)

    • Build the project to generate a new JSON file from scratch

    • Commit the recreated JSON file to your VCS (e.g. git)

    • Delete all previously created ObjectBox databases (e.g. for Android, delete the app’s data or uninstall the app)

    While this is a simple approach, it has its obvious disadvantages. For example for a published app, all existing data would be lost.

    ObjectBox Admin

    The ObjectBox Admin web app (formerly Data Browser) is an easy way to view what's happening in your database of your app using a web browser. Browse your app's data and gain insights.

    ObjectBox Admin Web App

    The ObjectBox Admin web app allows you to

    • view data objects and schema of your database inside a regular web browser,

    example/tasks/main.py
    
    @Entity()
    class Task:
        id = Id()
        text = String()
        date_created = Date(py_type=int)
        date_finished = Date(py_type=int)
    NoteActivity.java
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        notesBox = ObjectBox.get().boxFor(Note.class);
        ...
    }
    NoteActivity.kt
    public override fun onCreate(savedInstanceState: Bundle?) {
        ...
        notesBox = ObjectBox.boxStore.boxFor()
        ...
    }
    NoteActivity.java
    private void addNote() {
        ...
        Note note = new Note();
        note.setText(noteText);
        note.setComment(comment);
        note.setDate(new Date());
        notesBox.put(note);
        Log.d(App.TAG, "Inserted new note, ID: " + note.getId());
        ...
    }
    NoteActivity.kt
    private fun addNote() {
        ...
        val note = Note(text = noteText, comment = comment, date = Date())
        notesBox.put(note)
        Log.d(App.TAG, "Inserted new note, ID: " + note.id)
        ...
    }
    lib/objectbox.dart
    Future<void> addNote(String text) => _noteBox.putAsync(Note(text));
    example/tasks/main.py
    def add_task(self, text: str):
        task = Task(text=text, date_created=now_ms())
        self._task_box.put(task)
    NoteActivity.java
    OnItemClickListener noteClickListener = new OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Note note = notesAdapter.getItem(position);
            notesBox.remove(note);
            Log.d(App.TAG, "Deleted note, ID: " + note.getId());
            ...
        }
    };
    NoteActivity.kt
    private val noteClickListener = OnItemClickListener { _, _, position, _ ->
        notesAdapter.getItem(position)?.also {
            notesBox.remove(it)
            Log.d(App.TAG, "Deleted note, ID: " + it.id)
        }
        ...
    }
    lib/objectbox.dart
    Future<void> removeNote(int id) => _noteBox.removeAsync(id);
    example/tasks/main.py
    def remove_task(self, task_id: int) -> bool:
        is_removed = self._task_box.remove(task_id)
        return is_removed
    NoteActivity.java
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        // Query all notes, sorted a-z by their text.
        notesQuery = notesBox.query().order(Note_.text).build();
        ...
    }
    NoteActivity.kt
    public override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Query all notes, sorted a-z by their text.
        notesQuery = notesBox.query {
            order(Note_.text)
        }
        ...
    }
    lib/objectbox.dart
    Stream<List<Note>> getNotes() {
      // Query for all notes, sorted by their date.
      // https://docs.objectbox.io/queries
      final builder = _noteBox.query().order(Note_.date, flags: Order.descending);
      ...
    example/tasks/main.py
    def __init__(self):
        # ...
        self._query = self._task_box.query().build()
    NoteActivity.java
    private void updateNotes() {
        List<Note> notes = notesQuery.find();
        notesAdapter.setNotes(notes);
    }
    NoteActivity.kt
    private fun updateNotes() {
        val notes = notesQuery.find()
        notesAdapter.setNotes(notes)
    }
    lib/objectbox.dart
      ...
      // Build and watch the query,
      // set triggerImmediately to emit the query immediately on listen.
      return builder
          .watch(triggerImmediately: true)
          // Map it to a list of notes to be used by a StreamBuilder.
          .map((query) => query.find());
    }
    example/tasks/main.py
    def find_tasks(self):
        return self._query.find()
    note.setText("This note has changed.");
    notesBox.put(note);
    note.text = "This note has changed."
    notesBox.put(note)
    note.text = "This note has changed.";
    _noteBox.putAsync(note);
    task.text = "This task has changed."
    self._task_box.put(task)
    Date date;
    ...
    }
    var comment: String? = null,
    var date: Date? = null
    )
    }
    do a text search for the UID and check if the ID part is correct for all UID occurrences
    basics
    ObjectBox example project
    how-to guide
    Android Auto Backup
    supplied as the initial database file
    # Make sure to install objectbox >= 4.0.0
    $ pip install --upgrade objectbox
    $ ls example
    ollama 
    tasks
    vectorsearch-cities
    
    $ cd tasks
    $ python main.py
    
    Welcome to the ObjectBox tasks-list app example. Type help or ? for a list of commands.
    > 
    lib/objectbox.dart
    class ObjectBox {
      late final Store _store;
      late final Box<Note> _box;
      
      ObjectBox._create(this._store) {
        _noteBox = Box<Note>(_store);
        ...
    example/tasks/main.py
    class TasklistCmd(Cmd):
        # ...
    
        def __init__(self):
            # ...
            self._store = Store(directory="tasklist-db")
            self._task_box = self._store.box(Task)
    {
      "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
      "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
      "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
      "entities": [
        {
          "id": "1:6645479796472661392",
          "lastPropertyId": "5:8452815412120793251",
          "name": "Note",
          "properties": [
            {
              "id": "1:9211738071025439652",
              "name": "id",
              "type": 6,
              "flags": 1
            },
            {
              "id": "2:8804670454579230281",
              "name": "text",
              "type": 9
            },
            {
              "id": "4:1260602348787983453",
              "name": "date",
              "indexId": "1:9194998581973594219",
              "type": 10,
              "flags": 8
            },
            {
              "id": "5:8452815412120793251",
              "name": "authorId",
              "indexId": "2:5206397068544851461",
              "type": 11,
              "flags": 520,
              "relationTarget": "Author"
            }
          ],
          "relations": []
        },
        {
          "id": "2:8096888707432154961",
          "lastPropertyId": "2:388290850215565763",
          "name": "Author",
          "properties": [
            {
              "id": "1:2456363380762264329",
              "name": "id",
              "type": 6,
              "flags": 1
            },
            {
              "id": "2:388290850215565763",
              "name": "name",
              "type": 9
            }
          ],
          "relations": []
        }
      ],
      "lastEntityId": "2:8096888707432154961",
      "lastIndexId": "2:5206397068544851461",
      "lastRelationId": "0:0",
      "lastSequenceId": "0:0",
      "modelVersion": 5,
      "modelVersionParserMinimum": 5,
      "retiredEntityUids": [],
      "retiredIndexUids": [],
      "retiredPropertyUids": [],
      "retiredRelationUids": [],
      "version": 1
    }
    // If lastEntityId in the model file is incorrect:
    io.objectbox.exception.DbSchemaException:
     DB's last entity ID 4 is higher than 3 from model
    
    // If the UID of an entity in the model file has changed:
    io.objectbox.exception.DbSchemaException:
     Incoming entity ID 1:6645479796472661392 does not match existing UID 8096888707432154961

    display additional information,

  • and download objects in JSON format.

  • Admin comes in two variants: as a standalone desktop app (Docker image) and embedded in the Android library.

    The web app runs directly on your device or on your development machine. Behind the scenes, this is done by embedding a lightweight HTTP server into ObjectBox. It runs completely local (no Cloud whatsoever) and you can simply open the Admin in your Browser.

    The Admin web app with the Data page open.

    Run via Docker

    Latest changes (2025-07-05):

    • Add class and dependency diagrams to the schema page (view and download)

    • Improved data view for large vectors by displaying only the first elements and the full vector in a dialog

    • Detects images stored as bytes and shows them as such (PNG, GIF, JPEG, SVG, WEBP)

    To run Admin on a desktop operating system, e.g. your development machine, you can launch ObjectBox Admin instantaneously using the official ObjectBox Admin Docker image objectboxio/admin. This requires a running Docker Engine or Docker Desktop. If not done, install and start Docker Engine (Linux, Windows/WSL2) or Docker Desktop (Windows, macOS) capable of running a Linux/x86_64 image.

    There's a known issue on macOS and Windows with Docker and changes to the filesystem. Thus, to see changes made to the database, Admin needs to be restarted.

    Recommended, but the below script needs to be run from a Linux distribution (for example Ubuntu through WSL2 on Windows) or macOS. To run from Windows PowerShell, see the "Run Docker manually" tab.

    2KB
    objectbox-admin.sh
    Open
    objectbox-admin.sh Shell Front-End for ObjectBox Admin Docker Image

    Download the script objectbox-admin.sh via the link above. Make it executable (e.g. chmod +x objectbox-admin.sh). Copy it to some place (e.g./usr/local/bin) and then run it.

    Then you can have a quick look at the options of the script:

    Basically you can optionally select the path to an ObjectBox database and the mapping of the local HTTP port (e.g. to open multiple Admins to analyze multiple databases).

    So to run the script, either change to the directory where the objectbox directory with the database file data.mdb exists:

    Or pass the path to it as an argument:

    If you see the error failed: port is already allocated. try to use a different local port. E.g. to use port 8082:

    Note: If you run the script for the first time Docker will download the ObjectBox Admin image automatically from Docker Hub. If run again the download is skipped as the image has been cached in your local image repository.

    The Docker image is available at objectboxio/admin on Docker Hub. We recommend to use the latest tag.

    Linux/macOS/Windows WSL2 command line

    Replace /path/to/db with the actual path to the directory containing the data.mdb file.

    If you need to use a different local port other than 8081, modify the first number of -p accordingly. E.g. -p 8082:8081 lets you open the web-app at http://localhost:8082.

    Once Admin has started, open the local URL printed by the script (typically http://127.0.0.1:8081) in your browser. You should see the Data page for an entity type.

    Admin for Android

    Works for Android apps built with ObjectBox for Java or Flutter

    We strongly recommend using Admin only for debug builds as it ships with additional resources and configuration not intended for production code.

    Modify the app's Gradle build file to add the dependency and change the “io.objectbox” plugin to be applied after the dependencies block:

    If the plugin is not applied afterwards, the build will fail with a duplicate files error (like Duplicate files copied in APK lib/armeabi-v7a/libobjectbox.so) because the plugin fails to detect and adds the objectbox-android library.

    Modify the Gradle build file of the Flutter Android app to add the dependency:

    To avoid a version mismatch on updates, we suggest to change the dependency on the objectbox Dart package from a range of versions to a concrete version:

    Finally, after creating the store, to start Admin:

    Create an Admin instance and call start:

    Create an Admin instance and keep a reference, optionally close it once done using the web app:

    Info: added Android manifest permissions

    For your information, these are the permissions the objectbox-android-objectbrowser dependency automatically adds to AndroidManifest.xml:

    If the dependency is only used in debug builds as recommended above, these permissions will not be added to your release build.

    Browse data on your test device

    When Admin is started it will print the URL where to access the web app to the logs, e.g. something like:

    The URL can be opened on the device or emulator. To open the web app on your dev machine, see the instructions below.

    For ObjectBox for Java, the app also displays a notification to access Admin. (Don't see the notification on Android 13 or newer? Try to manually turn on notifications for the app!) Tapping it will launch a service to keep the app alive and opens the Admin web app in the web browser on the device.

    Stop the keep-alive service from the notification.

    Browse data on your dev machine

    To open the web app on your development machine find the Admin URL log message as noted above.

    Then, on your dev machine, use the ADB command to forward the port (or whichever you like) to that port of your device. If the default port 8090 is used, the command looks like this:

    Then open the web app URL in a web browser on your dev machine.

    Download Objects

    To download all objects of the currently viewed box tap the download all button at the very bottom. The exported data is in JSON format.

    The download option for objects.

    $ objectbox-admin.sh --help
    
    usage: objectbox-admin.sh [options] [<database-directory>]
    
    <database-directory> ( defaults to ./objectbox ) should contain an objectbox "data.mdb" file.
    
    Available (optional) options:
     [--port <port-number>] Mapped bind port to localhost (defaults to 8081)
    app/build.gradle
    dependencies {
        // Manually add objectbox-android-objectbrowser only for debug builds,
        // and objectbox-android for release builds.
        debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
        releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
    }
    
    // Apply the plugin after the dependencies block so it picks up 
    // and does not add objectbox-android.
    apply plugin: 'io.objectbox'
    // Or using Kotlin DSL:
    apply(plugin = "io.objectbox")
    android/app/build.gradle
    // Tell Gradle to exclude the Android library (without Admin)
    // that is added by the objectbox_flutter_libs package for debug builds.
    configurations {
        debugImplementation {
            exclude group: 'io.objectbox', module: 'objectbox-android'
        }
    }
    
    dependencies {
        // Add the Android library with ObjectBox Admin only for debug builds.
        // Note: when the objectbox package updates, check if the Android
        // library below needs to be updated as well.
        // TODO Replace <version> with the one noted in the release notes (https://github.com/objectbox/objectbox-dart/releases)
        debugImplementation("io.objectbox:objectbox-android-objectbrowser:<version>")
    }
    dependencies:
      # Note: when updating objectbox, check the release notes (https://github.com/objectbox/objectbox-dart/releases)
      # if objectbox-android-objectbrowser in android/app/build.gradle has to be updated.
      objectbox: x.y.z # TODO Replace with valid version
      objectbox_flutter_libs: any
    boxStore = MyObjectBox.builder().androidContext(this).build();
    if (BuildConfig.DEBUG) {
        boolean started = new Admin(boxStore).start(this);
        Log.i("ObjectBoxAdmin", "Started: " + started);
    }
    if (Admin.isAvailable()) {
      // Keep a reference until no longer needed or manually closed.
      admin = Admin(store);
    }
    
    // (Optional) Close at some later point.
    admin.close();
    <!-- Required to provide the web interface -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Required to run keep-alive service when targeting API 28 or higher -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <!-- When targeting API level 33 or higher to post the initial Admin notification -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    ObjectBox Admin running at URL: http://127.0.0.1:8090/index.html
    adb forward tcp:8090 tcp:8090
    Windows PowerShell/Command Prompt

    Note: Make sure Docker Desktop runs with WSL2 instead of Hyper-V.

    Replace C:\path\to\db with the actual path to the directory containing the data.mdb file. Note this uses Windows-style backslashes.

    Note the user id (-u) mapping is omitted on Windows. The port can be changed as written above.

    Data Observers & Rx

    How to observe box and query changes using ObjectBox with Java or Dart, how to integrate with RxJava.

    On this page:

    • ObjectBox Java - Data Observers and Reactive Extensions

    • ObjectBox Dart - Reactive Queries

    ObjectBox Java - Data Observers and Reactive Extensions

    ObjectBox for Java makes it easy for your app to react to data changes by providing:

    • data observers,

    • reactive extensions,

    • and an optional library to work with RxJava.

    This makes setting up data flows easy while taking care of threading details.

    Reactive Observers: A First Example

    Let’s start with an example to demonstrate what you can do with reactive data observers:

    The first line creates a regular query to get Task objects where task.complete == false. The second line connects an observer to the query. This is what happens:

    • the query is executed in the background

    • once the query finishes the observer gets the result data

    • whenever changes are made to Task objects in the future, the query will be executed again

    Now, let’s dive into the details.

    Data Observers Basics

    When objects change, ObjectBox notifies subscribed data observers. They can either subscribe to changes of certain object types (via BoxStore) or to query results. To create a data observer you need to implement the generic io.objectbox.reactive.DataObserver interface:

    This observer will be called by ObjectBox when necessary: typically shortly after subscribing and when data changes.

    Note: onData() is called asynchronously and decoupled from the thread causing the data change (like the thread that committed a transaction).

    Observing General Changes

    BoxStore allows a DataObserver to subscribe to object types. Let’s say we have a to-do list app where Task objects get added. To get notified when Task objects are added in another place in our app we can do the following:

    Here onData() is not called with anything useful as data. If you need more than being notified, like to get a list of Task objects following the above example, read on to learn how to observe queries.

    Note: there is also subscribe() which takes no arguments. It subscribes the observer to receive changes for all available object classes.

    Observing Queries

    ObjectBox let’s you to find the objects matching certain criteria. Queries are an essential part of ObjectBox: whenever you need a specific set of data, you will probably use a query.

    Combining queries and observers results in a convenient and powerful tool: query observers will automatically deliver fresh results whenever changes are made to entities in a box. Let’s say you display a list of to-do tasks in your app. You can use a DataObserver to get all tasks that are not yet completed and pass them to a method updateUi() (note that we are using lambda syntax here):

    So when is our observer lambda called? Immediately when an observer is subscribed, the query will be run in a separate thread. Once the query result is available, it will be passed to the observer. This is the first call to the observer.

    Now let’s say a task gets changed and stored in ObjectBox. It doesn't matter where and how; it might be the user who marked a task as completed, or some backend thread putting additional tasks during synchronization with a server. In any case, the query will notify all observers with (potentially) updated query results.

    Note that this pattern can greatly simplify your code: there is a single place where your data comes in to update your user interface. There is no separate initialization code, no wiring of events, no re-running queries, etc.

    See the documentation for more details.

    Canceling Subscriptions

    When you call observer(), it returns a subscription object implementing the io.objectbox.reactive.DataSubscription interface:

    Keep a reference to the DataSubscription for as long as results should be received, otherwise it can be garbage collected at any point. Also call cancel() on it once the observer should not be notified anymore, e.g. when leaving the current screen:

    If you have more than one query subscription, you might find it useful to create a DataSubscriptionList instance instead to keep track of multiple DataSubscription objects. Pass the list to the query.subscribe(subList) overload. A basic example goes like this:

    Note: On Android, you would typically create the subscription in one of the onCreate()/onStart()/onResume() lifecycle methods and cancel it in its counterpart onDestroy()/onStop()/onPause().

    Observers and Transactions

    Observer notifications occur after a transaction is committed. For some scenarios it is especially important to know transaction bounds. If you call box.put() or remove() individually, an implicit transaction is started and committed. For example, this code fragment would trigger data observers on User.class twice:

    There are several ways to combine several operations into one transaction, for example using one of the runInTx() or callInTx() methods in the BoxStore class. For our simple example, we can simply use an overload of put() accepting multiple objects:

    This results in a single transaction and thus in a single DataObserver notification.

    Reactive Extensions

    In the first part you saw how data observers can help you keep your app state up to date. But there is more: ObjectBox comes with simple and convenient reactive extensions for typical tasks. While most of these are inspired by RxJava, they are not actually based on RxJava. ObjectBox brings its own features because not all developers are familiar with RxJava (for the RxJava ObjectBox library see below). We do not want to impose the complexity (Rx is almost like a new language to learn) and size of RxJava (~10k methods) on everyone. So, let’s keep it simple and neat for now.

    Thread Scheduling

    On Android, UI updates must occur on the main thread only. Luckily, ObjectBox allows to switch the observer from a background thread over to the main thread. Let’s take a look on a revised version of the to-do task example from above:

    Where is the difference? The additional on() call is all that is needed to tell where we want our observer to be called. AndroidScheduler.mainThread() is a built-in scheduler implementation. Alternatively, you can create an AndroidScheduler using a custom Looper, or build a fully custom scheduler by implementing the io.objectbox.reactive.Scheduler interface.

    Transforming Data

    Maybe you want to transform the data before you hand it over to an observer. Let’s say, you want to keep track of the count of all stored objects for each type. The BoxStore subscription gives you the classes of the objects, and this example shows you how to transform them into actual object counts:

    Note that the transform operation takes a Class object and returns a Long number. Thus the DataObserver receives the object count as a Long parameter in onData().

    While the lambda syntax is nice and brief, let’s look at the io.objectbox.reactive.Transformer interface for clarification of what the transform() method expects as a parameter:

    Some additional notes on transformers:

    • Transforms are not required to actually “transform” any data. Technically, it is fine to return the same data that is received and just do some processing with (or without) it.

    • Transformers are always executed asynchronously. It is fine to perform long lasting operations.

    ErrorObserver

    Maybe you noticed that a transformer may throw any type of exception. Also, a DataObserver might throw a RuntimeException. In both cases, you can provide an ErrorObserver to be notified about an exception that occurred. The io.objectbox.reactive.ErrorObserver is straight-forward:

    To specify your ErrorObserver, simply call the onError() method after subscribe().

    Single Notifications vs. Only-Changes

    When you subscribe to a query, the DataObserver gets both of the following by default:

    • Initial query results (right after subscribing)

    • Updated query results (underlying data was changed)

    Sometimes you may by interested in only one of those. This is what the methods single() and onlyChanges() are for (call them after subscribe()). Single subscriptions are special in the way that they are cancelled automatically once the observer is notified. You can still cancel them manually to ensure no call to the observer is made at a certain point.

    Weak References

    Sometimes it may be nice to have a weak reference to a data observer. Note that for the sake of a deterministic flow, it is advisable to cancel subscriptions explicitly whenever possible. If that does not scare you off, use weak() after subscribe().

    Threading overview

    To summarize threading as discussed earlier:

    • Query execution runs on a background thread (exclusive for this task)

    • DataTransformer runs on a background thread (exclusive for this task)

    • DataObserver and ErrorObserver run on a background thread unless a scheduler is specified via the on()

    ObjectBox RxJava Extension Library

    By design, there are zero dependencies on any Rx libraries in the core of ObjectBox. As you saw before ObjectBox gives you simple means to transform data, asynchronous processing, thread scheduling, and one time (single) notifications. However, you still might want to integrate with the mighty RxJava 2 (we have no plans to support RxJava 1). For this purpose we created the ObjectBox RxJava extension library:

    It provides the classes RxQuery and RxBoxStore. Both offer static methods to subscribe using RxJava means.

    For general object changes, you can use RxBoxStore to create an Observable. RxQuery allows to subscribe to query objects using:

    • Flowable

    • Observable

    • Single

    Example usage:

    The extension library is open-source and available .

    ObjectBox Dart - Reactive Queries

    You can build a Stream from a query to get notified any time there is a change to the box of any of the queried entities:

    For a Flutter app you typically want to get the latest results immediately when listening to the stream, and also get a list of objects instead of a query instance:

    Troubleshooting

    Solutions for common issues with ObjectBox for Java and Dart

    Unresolved reference: MyObjectBox (class not found, etc.)

    MyObjectBox is a generated class, so make sure the project builds successfully. Resolve any other build errors.

    Check that the project is configured correctly. E.g. for Kotlin, check that the kapt plugin is applied (apply plugin: 'kotlin-kapt') before the ObjectBox plugin.

    android-apt is known to cause problems, try to remove it.

    Merge conflict or DbSchemaException after concurrent data model modifications

    If your team makes concurrent modifications to the data model (e.g. adding/removing entities or properties) it may clash with your changes. Read the on how to resolve the conflicts.

    DbSchemaException: incoming ID does not match existing UID

    Creating a Store throws a DbSchemaException and a message like

    • Incoming entity ID does not match existing UID

    • Incoming property ID does not match existing UID

    • Incoming index ID does not match existing UID

    This means there is a conflict between the data model defined in your code (using @Entity classes) and the data model of the existing database file.

    For example for an entity, this message indicates the unique identifier (UID) of an entity is not the same as before. Entity UIDs are auto-assigned by ObjectBox and stored in objectbox-models/default.json for Java or lib/objectbox-model.json for Dart. Look for the id property which contains the UID in the second part, the first part is the ID (the format is ID:UID).

    Read the on why this can happen and how to resolve such conflicts.

    DbSchemaException after switching git branch (no concurrent data model modifications)

    See below.

    DbSchemaException: DB's last ID is higher

    Creating a Store throws a DbSchemaException and a message like

    • DB's last entity ID is higher than from model

    • DB's last property ID is higher than the incoming one

    • DB's last index ID is higher than from model

    This means, there is a conflict between the data model defined in your code (using @Entity classes) and the data model of the existing database file.

    For example for an entity, this message indicates that the database already contains one or more entities with IDs higher than the lowest one of the model in your code. Entity IDs are auto-assigned by ObjectBox and stored in objectbox-models/default.json for Java or lib/objectbox-model.json for Dart. Look for the lastEntityId value, the highest used entity ID is contained in the first part, the second part is the UID (the format is ID:UID).

    Read the on why this can happen and how to resolve such conflicts.

    DbFullException: Could not put

    This is thrown when applying a transaction (e.g. putting an object) would exceed the (Java)/ (Dart) configured for the store.

    By default, this is 1 GB, which should be sufficient for most applications. In general, a maximum size prevents the database from growing indefinitely when something goes wrong (for example data is put in an infinite loop).

    This value can be changed, so increased or also decreased, each time when opening a store:

    Couldn’t find “libobjectbox.so”

    This can have various reasons. In general check your ABI filter setup in your Gradle build file.

    If your app explicitly ships code for "armeabi": For Android, ObjectBox comes with binaries for “armeabi-v7a” and “arm64-v8a” ABIs. We consider “armeabi” to be outdated and thus do not support it. Check if you have a Gradle config like abiFilters "armeabi", which is causing the problem (e.g. remove it or change it to “armeabi-v7a”).

    If your app uses split APKs or App Bundle: some users might have sideloaded your APK that includes the library for a platform that is incompatible with the one of their device. See for workarounds.

    Version conflict with ‘com.google.code.findbugs:jsr305’

    If you are doing Android instrumentation (especially with Espresso), you may get a warning like this: Error:Conflict with dependency ‘com.google.code.findbugs:jsr305’ in project ‘:app’. Resolved versions for app (3.0.2) and test app (2.0.1) differ. See for details.

    You can easily resolve the version conflict by adding this Gradle dependency: androidTestCompile 'com.google.code.findbugs:jsr305:3.0.2'

    .

    Incompatible property type

    Check the if you get an exception like: io.objectbox.exception.DbException: Property […] is not compatible to its previous definition. Check its type.

    or

    Cannot change the following flags for Property

    Flutter iOS builds for armv7 fail with "ObjectBox does not contain that architecture"

    To resolve the build error, configure Architectures in your Xcode project like described in for Flutter.

    Error Codes

    Sometimes you might get an error code along with a error message. Typically, you will find error codes in parenthesis. Example:

    Could not prepare directory: objectbox (30)

    This error code comes from the OS, and gives you additional information; e.g. 30 tells that you tried to init a database in a read-only location of the file system, which cannot work.

    Here's the list of typical OS errors:

    Help with other issues

    If you believe to have found a bug or missing feature, please create an issue.

    For Java/Kotlin:

    For Flutter/Dart:

    If you have a usage question regarding ObjectBox, please post on Stack Overflow.

    cd objectbox-c/examples/cpp-gen
    objectbox-admin.sh
    objectbox-admin.sh objectbox-c/examples/cpp-gen
    objectbox-admin.sh --port 8082
    docker run --rm -it --volume /path/to/db:/db -u $(id -u):$(id -g) --publish 8081:8081 objectboxio/admin:latest
    docker run --rm -it --volume C:\path\to\db:/db --publish 8081:8081 objectboxio/admin:latest
    once updated query results are in, they are propagated to the observer
  • the observer is called on Android’s main thread

  • method.
    build queries
    subscribe()-method
    GitHub
    DB's last
    relation
    ID is higher than from model

    Interrupted system call

    5

    EIO

    I/O error

    6

    ENXIO

    No such device or address

    7

    E2BIG

    Argument list too long

    8

    ENOEXEC

    Exec format error

    9

    EBADF

    Bad file number

    10

    ECHILD

    No child processes

    11

    EAGAIN

    Try again

    12

    ENOMEM

    Out of memory

    13

    EACCES

    Permission denied

    14

    EFAULT

    Bad address

    15

    ENOTBLK

    Block device required

    16

    EBUSY

    Device or resource busy

    17

    EEXIST

    File exists

    18

    EXDEV

    Cross-device link

    19

    ENODEV

    No such device

    20

    ENOTDIR

    Not a directory

    21

    EISDIR

    Is a directory

    22

    EINVAL

    Invalid argument

    23

    ENFILE

    File table overflow

    24

    EMFILE

    Too many open files

    25

    ENOTTY

    Not a typewriter

    26

    ETXTBSY

    Text file busy

    27

    EFBIG

    File too large

    28

    ENOSPC

    No space left on device

    29

    ESPIPE

    Illegal seek

    30

    EROFS

    Read-only file system

    31

    EMLINK

    Too many links

    32

    EPIPE

    Broken pipe

    33

    EDOM

    Math argument out of domain of func

    34

    ERANGE

    Math result not representable

    Error code

    Errno

    Description

    1

    EPERM

    Operation not permitted

    2

    ENOENT

    No such file or directory

    3

    ESRCH

    No such process

    4

    meta model docs
    meta model docs
    meta model docs
    maxSizeInKByte
    maxDBSizeInKB
    or add one
    App Bundle, split APKs and Multidex
    http://g.co/androidstudio/app-test-app-conflict
    Background info
    data model migration guide
    Only 64-bit iOS devices are supported.
    Getting Started
    FAQ
    https://github.com/objectbox/objectbox-java/issues
    https://github.com/objectbox/objectbox-dart/issues
    https://stackoverflow.com/questions/tagged/objectbox

    EINTR

    // Keep a reference until the subscription is cancelled
    // to avoid garbage collection.
    private DataSubscription subscription;
    
    // ...
    
    // Build a regular query.
    Query<Task> query = taskBox.query().equal(Task_.complete, false).build();
    // Subscribe to its results.
    subscription = query.subscribe()
         .on(AndroidScheduler.mainThread())
         .observer(data -> updateResultDisplay(data));
    
    // Cancel to no longer receive updates (e.g. leaving screen).
    subscription.cancel();
    
    private void updateResultDisplay(List<Task> tasks) {
       // Do something with the given tasks.
    }
    public interface DataObserver<T> {
        void onData(T data);
    }
    DataObserver<Class<Task>> observer = new DataObserver<Class<Task>>() {
        @Override public void onData(Class<Task> data) {
            // TODO Do something, e.g. run a query.
            // Just observing a query can also be done 
            // more easily, read on!
            List<Task> results = store.boxFor(Task.class)
                .query(Task_.text.contains("TODO"))
                .build()
                .find(offset, limit);
        }
    };
    // Keep the subscription while used 
    // to avoid garbage collection of the observer.
    subscription = boxStore.subscribe(Task.class).observer(observer);
    val observer = object : DataObserver<Class<Task>> {
        override fun onData(data: Class<Task>) {
            // TODO Do something, e.g. run a query.
            // Just observing a query can also be done 
            // more easily, read on!
            val results = store.boxFor(Task::class)
                .query(Task_.text.contains("TODO"))
                .build()
                .find(offset, limit)
        }
    }
    // Keep the subscription while used 
    // to avoid garbage collection of the observer.
    subscription = store.subscribe(Task::class.java).observer(observer)
    Query<Task> query = taskBox.query().equal(Task_.completed, false).build();
    subscription = query.subscribe().observer(data -> updateResultDisplay(data));
    public interface DataSubscription {
        void cancel();
        boolean isCanceled();
    }
    // Keep a reference for as long as updates should be received.
    DataSubscription subscription = boxStore.subscribe().observer(myObserver);
    
    // To no longer receive updates (e.g. leaving screen):
    subscription.cancel();
    private DataSubscriptionList subscriptions = new DataSubscriptionList();
    
    protected void onStart() {
      super.onStart();
      Query<A> queryA = boxA.query().build();
      queryA.subscribe(subscriptions).observe(someObserverForAs);
      Query<B> queryB = boxB.query.build();
      queryB.subscribe(subscriptions).observe(someObserverForBs);
    }
    
    protected void onStop() {
      super.onStop();
      // Cancels both subscriptions (for A and B).
      subscriptions.cancel();
    }
    box.put(friendUser);
    box.put(myUser);
    
    // Log:
    // User friendUser put.
    // Observers of User called.
    // User myUser put.
    // Observers of User called.
    box.put(friendUser, myUser);
    
    // Log:
    // Users friendUser and myUser put.
    // Observers of User called.
    Query<Task> query = taskBox.query().equal(Task_.complete, false).build();
    // updateResultDisplay is always called on the Android main thread.
    subscription = query.subscribe()
        .on(AndroidScheduler.mainThread())
        .observer(data -> updateResultDisplay(data));
    subscription = boxStore.subscribe()
        .transform(clazz -> return boxStore.boxFor(clazz).count())
        .observer(count -> updateCount(count));
    public interface DataTransformer<FROM, TO> {
        TO transform(FROM source) throws Exception;
    }
    public interface ErrorObserver {
        void onError(Throwable th);
    }
    implementation "io.objectbox:objectbox-rxjava:$objectboxVersion"
    // Keep a reference until the disposable is disposed
    // to avoid garbage collection.
    private Disposable disposable;
    
    // ...
    
    Query query = box.query().build();
    disposable = RxQuery.observable(query).subscribe(this);
    Stream<Query<Note>> watchedQuery = box.query(condition).watch();
    final sub1 = watchedQuery.listen((Query<Note> query) {
      // This gets triggered any time a box of any of the queried entities
      // has changes, e.g. objects are put or removed.
      // Call any query method here, for example:
      print(query.count());
      print(query.find());
    });
    ...
    sub1.cancel(); // Cancel the subscription after your code is done.
    // Build and watch the query,
    // set triggerImmediately to emit the query immediately on listen.
    return box.query(condition)
        .watch(triggerImmediately: true)
        // Map it to a list of objects to be used by a StreamBuilder.
        .map((query) => query.find());
    MyObjectBox.builder()
        .androidContext(context)
        .maxSizeInKByte(1024 * 1024 /* 1 GB */)
        .build()
    Store(getObjectBoxModel(), maxDBSizeInKB: 1024 * 1024 /* 1 GB */);

    Entity Annotations

    Instead of SQL, you define your data model by annotating your persistent object types on the programming language level.

    ObjectBox - Database Persistence with Entity Annotations

    ObjectBox is a database that persists objects, which we sometimes also call entities.

    To let ObjectBox know which classes are entities you annotate them with @Entity. This annotation identifies the class User in the following example as a persistable entity. This will trigger ObjectBox to generate persistence code tailored for this class:

    Note:

    • It’s often good practice to model entities as “dumb” data classes (POJOs) with just properties.

    • Entities must have a no-args constructor, or for better performance, a constructor with all properties as arguments. In the above example, a default, no-args constructor is generated by the compiler.

    Note:

    • It’s often good practice to model entities as “dumb” data classes (POJOs) with just properties.

    • Entities must have a no-args constructor, or for better performance, a constructor with all properties as arguments. In the above example, a default, no-args constructor is generated by the compiler. For Kotlin data classes this can be achieved by

    It’s often good practice to model entities as “dumb” data classes with just properties.

    For ObjectBox to be able to construct objects read from the database, entities must have a no-args constructor or a constructor with argument names matching the properties, for example:

    Available Property types include: Bool, Char, Bytes, String, Int8/16/32/64, Float32/64 *Vector and *List variants (e.g. Float32Vector)

    Object IDs: @Id

    In ObjectBox, entities must have one 64-bit integer ID property with non-private visibility (or non-private getter and setter method) to efficiently get or reference objects.

    If you need to use another type for IDs (such as a string UID given by a server), model them as regular properties and use to look up objects by your application-specific ID. Also, make sure to index the property, and if it's a string use a case-sensitive condition, to speed up lookups. To prevent duplicates it is also possible to enforce a value for this secondary ID.

    ID properties are unique and indexed by default.

    When you put a new object you do not assign an ID. By default IDs for new objects are assigned by ObjectBox. See the page on for details.

    If you need to assign IDs by yourself have a look at and what side effects apply.

    Make entity data accessible

    ObjectBox needs to access the data of your entity’s properties (e.g. in the generated code). You have two options:

    1. Make sure properties do not have private visibility.

    2. Provide standard getters (your IDE can generate them easily).

    To improve performance when ObjectBox constructs your entities, you might also want to provide an all-properties constructor.

    Supported property types

    ObjectBox supports a wide range of property types including integers, floats, strings, dates, lists, and flex properties for dynamic data. See the dedicated page for details:

    If the built-in types don't cover your needs, you can use .

    Basic annotations for entity properties

    Transient

    @Transient marks properties that should not be persisted. In Java static or transient properties will also not be persisted.

    NameInDb

    Only available for Java/Kotlin at the moment

    @NameInDb lets you define a name on the database level for a property. This allows you to rename the property without affecting the property name on the database level.

    • We recommend to rename properties and even entities instead.

    • @NameInDb only works with inline constants to specify a column name.

    Property Indexes

    Annotate a property with @Index to create a database index for the corresponding database column. This can improve performance when querying for that property.

    @Index is currently not supported for String[], byte[], float and double

    An index stores additional information in the database to make lookups faster. As an analogy, we could look at Java-like programming languages where you store objects in a list. For example, you could store persons using a List<Person>. Now, you want to search for all persons with a specific name so you would iterate through the list and check for the name property of each object. This is an O(N) operation and thus doesn't scale well with an increasing number of objects. To make this more scalable you can introduce a second data structure Map<String, Person> with the name as a key. This will give you a constant lookup time (O(1)). The downside of this is that it needs more resources (here: RAM) and slows down add/remove operations on the list a bit. These principles can be transferred to database indexes, just that the primary resource consumed is disk space.

    Index types (String)

    For scalar properties, ObjectBox uses a value-based index. Because String properties typically require more storage space than scalar values, by default ObjectBox uses a hash index for strings instead.

    To override the default and use a value-based index for a String property, specify the index type:

    Keep in mind that for String, depending on the length of your values, a value-based index may require more storage space than the default hash-based index.

    ObjectBox supports these index types:

    • Not specified or DEFAULT Uses the best index based on property type (HASH for String, VALUE for others).

    • VALUE Uses property values to build the index. For String, this may require more storage than a hash-based index.

    • HASH Uses a 32-bit hash of property values to build the index. Occasional collisions may occur which should not have any performance impact in practice. Usually, a better choice than HASH64, as it requires less storage.

    Limits of hash-based indexes: Hashes work great for equality checks, but not for "starts with" type conditions. If you frequently use those, you should use value-based indexes instead.

    Vector Index for Nearest Neighbor Search

    To enable nearest neighbor search, a special index type for vector properties is available:

    Unique constraints

    Annotate a property with @Unique to enforce that values are unique before an entity is put:

    A put() operation will abort and throw a UniqueViolationException if the unique constraint is violated:

    For a single property it is possible to specify that a conflicting object should be replaced instead of an exception being thrown:

    The REPLACE strategy will add a new object with a different ID. As relations (ToOne/ToMany) reference objects by ID, if the previous object was referenced in any relations, these need to be updated manually.

    Unique constraints are based on an index, so it is possible to further configure the index with an @Index annotation.

    Change database type

    Use @Type in Java/Kotlin or the type attribute on @Property in Dart to override how the value of a property is stored and interpreted in the database.

    Relations

    Creating to-one and to-many relations between objects is possible as well, see the documentation for details.

    Triggering code generation

    Once your entity schema is in place, you can .

    adding default values for all parameters.
    (Technically this is only required if adding properties to the class body, like custom or transient properties or relations, but it's a good idea to do it always.)
    @Index is currently not supported for Array<String>, ByteArray , Float and Double

    @Index is currently not supported for double and listsList<String>, List<int>, Uint8List, Int8List

  • HASH64 Uses a 64-bit hash of property values to build the index. Requires more storage than HASH and thus should not be the first choice in most cases.

  • queries
    unique
    Object IDs
    how to switch to self-assigned IDs
    Property Types
    custom types and converters
    using @Uid annotations
    On-Device Vector Search
    Relations
    trigger the code generation process
    @Entity
    public class User {
        
        @Id
        private long id;
        
        private String name;
        
        // Not persisted:
        @Transient
        private int tempUsageCount;
        
       // TODO: getters and setters.
    }
    @Entity
    data class User(
            @Id var id: Long = 0,
            var name: String? = null,
            // Not persisted:
            @Transient var tempUsageCount: Int = 0
    )
    @Index()
    String name;
    @Entity()
    class User:
      name = String(index=Index())
    @Entity()
    class User {
      // Annotate with @Id() if name isn't "id" (case insensitive).
      int id = 0;
      String? name;
      
      // Not persisted:
      @Transient
      int tempUsageCount = 0;
    )
    User({this.id, this.name});
    @Entity()
    class User:
        id = Id
        name = String
        temp_usage_count = None
    @Entity
    public class User {
        @Id public long id;
        // Note: You can use the nullable java.lang.Long, but we do not recommend it.
        
        ...
    }
    @Entity
    data class User(
            @Id var id: Long = 0,
            ...
    )
    @Entity()
    class User {
      // Annotate with @Id() if name isn't "id" (case insensitive).
      int id;
    
      ...
    }
    @Entity()
    class User:
        id = Id  # Use either class Id or instance notation (e.g. Id())
    @Entity
    class StringIdEntity {
        @Id public long id;
        @Index public String uid;
        // Alternatively:
        // @Unique String uid;
    }
    
    StringIdEntity entity = box.query()
        .equal(StringIdEntity_.uid, uid, StringOrder.CASE_SENSITIVE)
        .build().findUnique()
    @Entity
    data class StringIdEntity(
            @Id var id: Long = 0,
            @Index var uid: String? = null
            // Alternatively:
            // @Unique uid: String? = null
    )
    
    val entity = box.query()
            .equal(StringIdEntity_.uid, uid, StringOrder.CASE_SENSITIVE)
            .build().findUnique()
    @Entity()
    class StringIdEntity(
      int id;
      
      @Index() // or alternatively use @Unique()
      String uid;
    )
    
    final objects = box.query(StringIdEntity_.uid.equals('...')).build().find();
    @Entity
    public class Order {
        
        // Option 1: field is not private.
        @Id long id;
        
        // Option 2: field is private, but getter is available.
        private String name;
        
        public ToOne<Customer> customer;
        public ToMany<Order> relatedOrders;
        
        // At minimum, provide a default constructor for ObjectBox.
        public Order() {
        }
    
        // Optional: all-properties constructor for better performance.
        // - make sure type matches exactly,
        // - for ToOne add its virtual ID property instead,
        // - for ToMany add no parameter.
        public Order(long id, String name, long customerId) {
            this.id = id;
            this.name = name;
            this.customer.setTargetId(customerId);
        }
        
        public String getName() {
            return this.name;
        }
    }
    // For Kotlin a data class with default values
    // meets all above requirements. 
    @Entity
    data class User(
            @Id var id: Long = 0,
            var name: String? = null
    )
    @Entity()
    class User {
      int id;
        
      String? _name;
      
      String get name {...}
      
      set name(String value) {...}
        
      User(this.id);
    }
    @Transient
    private int notPersisted;
    @Transient
    var notPersisted: Int = 0
    @Transient()
    int? notPersisted;
    @NameInDb("username")
    private String name;
    @NameInDb("username")
    var name: String? = null
    @Index
    private String name;
    @Index
    var name: String? = null
    @Index(type = IndexType.VALUE)
    private String name;
    @Index(type = IndexType.VALUE)
    var name: String? = null
    @Index(type: IndexType.value)
    String name;
    @Unique
    private String name;
    @Unique
    var name: String? = null
    @Unique()
    String? name;
    try {
        box.put(new User("Sam Flynn"));
    } catch (UniqueViolationException e) {
        // A User with that name already exists.
    }
    try {
        box.put(User("Sam Flynn"))
    } catch (e: UniqueViolationException) {
        // A User with that name already exists.
    }
    try {
        box.put(User('Sam Flynn'))
    } on UniqueViolationException catch (e) {
        // A User with that name already exists.
    }
    @Unique(onConflict = ConflictStrategy.REPLACE)
    private String name;
    @Unique(onConflict = ConflictStrategy.REPLACE)
    var name: String? = null
    @Unique(onConflict: ConflictStrategy.replace)
    String? name;
    // Store 64-bit integer as time in nanoseconds.
    @Type(DatabaseType.DateNano)
    private long timeInNanos;
    // Store 64-bit integer as time in nanoseconds.
    @Type(DatabaseType.DateNano)
    var timeInNanos: Long = 0
    // Time with nanosecond precision.
    @Property(type: PropertyType.dateNano)
    DateTime nanoDate;
    
    @Property(type: PropertyType.byte)
    int byte; // 1 byte
    
    @Property(type: PropertyType.short)
    int short; // 2 bytes
    
    @Property(type: PropertyType.char)
    int char; // 1 bytes
    
    @Property(type: PropertyType.int)
    int int32; // 4 bytes
    
    @Property(type: PropertyType.float)
    double float; // 4 bytes
    
    @Property(type: PropertyType.byteVector)
    List<int> byteList;
    class Tasks:
        date_started = Date(py_type=int)

    On-Device Vector Search

    Local on-device approximate nearest neighbor (ANN) search on high-dimensional vector properties

    Vector Search is currently available for Python, C, C++, Dart/Flutter, Java/Kotlin and Swift. Other languages will follow soon.

    Vector search is the task of searching for objects whose vector is near to a given input query vector. Applications include semantic/similarity search (often performs better than full text search (FTS)), multi-modal search (text, images, video), recommendation engines and various use cases in AI.

    You can use ObjectBox as a plain vector database and store only vectors and IDs. Or, you can have your entire data model stored in ObjectBox since it is also a full database. Choose anything in between that best suits your needs.

    To use ObjectBox as a vector database follow these 3 simple steps:

    1. Define your data model along with a vector index,

    2. Insert your data/vectors,

    3. Search for nearest neighbors.

    An Example: Cities and their Location

    To illustrate these steps, we will use a simplified example using cities throughout the next sections. Each city has a location expressed as latitude and longitude. And thus, we can search cities that are close to a certain point.

    In the diagram above, let's look at which are the closest cities to the red point, which is located at latitude 53.0 and longitude 15.0. One can intuitively see that Berlin is closest, followed by Copenhagen and Vienna. In search terms, Berlin, Copenhagen and Vienna are the 3 nearest neighbors to the given point.

    A city's location has only 2 dimensions, which is easy to grasp. While even 3D space can still be quite intuitive, we humans typically have a hard time when dimensions increase beyond that. But this is exactly where vector databases shine: they handle high-dimensional data with typically hundreds or thousands of dimensions.

    Data Model and Vector Index

    In order to enable efficient vector search, a vector database needs to "index" the data. This is true for ordinary data too, just that a vector index is a "special" index. ObjectBox relies on Hierarchical Navigable Small Worlds (HNSW), a state-of-the-art algorithm for approximate nearest neighbor (ANN) search that performs very fast and is very scalable.

    Hierarchical Navigable Small Worlds (HNSW) - Background Information

    HNSW spans a graph over all vectors by connecting each vector node to its closest neighbors. To search for the nearest neighbors of a given vector, you start at an arbitrary node and check which of its neighbors are closer to the given vector. This repeats until the closest nodes are identified. To make this scalable, HNSW uses layers: higher layers have less nodes to get closer to the destination vector faster. Once the closest node in a layer is identified, the algorithm moves down one layer. This repeats until the lowest layer which contains all nodes is reached and the actual approximate nearest neighbors are found.

    Approximate nearest neighbor (ANN) search: with high-dimensional vectors, exact nearest neighbor search is extremely time consuming (see also: ). Thus, approximate solutions like HNSW are the way to go in this domain. Typically, they come with a quality/performance trade-off. In the case of HNSW you have parameters to control that.

    Defining the data model in ObjectBox is straight-forward: in one of the supported programming languages, declare a City class and "annotate" (tag) it as an ObjectBox "Entity" (a persistable object type; check the guide for details). The class shall have the following members (properties): an obligatory ID, a name, and a location. The latter is expressed as a vector; after all we want to demonstrate vector search. This vector only has 2 dimensions: latitude and longitude.

    So, this is what a City data class with an HNSW index definition can look like:

    For C++, you define the data model using FlatBuffer schema files (see the for details):

    Once the ObjectBox Generator was run, it creates a City struct like this:

    As a starting point the index configuration only needs the number of dimensions. To optimize the index, you can supply additional options via the annotation later once you got things up and running:

    • dimensions (required): how many dimensions of the vector to use for indexing. This is a fixed value that depends on your specific use case (e.g. on your embedding model) and you will typically only use vectors of that exact dimension. For special use cases, you can insert vectors with a higher dimension. However, if the vector of an inserted object has less dimensions, it is completely ignored for indexing (it cannot be found).

    • distanceType: the algorithm used to determine the distance between two vectors. By default, (squared) Euclidean distance is used: d(v, w) = length(v - w) Other algorithms, based on cosine, Haversine distance and dot product, are available.

    • neighborsPerNode (aka "M" in HNSW terms): the maximum number of connections per node (default: 30). A higher number increases the graph connectivity which can lead to better results, but higher resources usage. Try e.g. 16 for faster but less accurate results, or 64 for more accurate results.

    There are also some advanced options available:

    • flags to turn on debug log output, to turn off SIMD padding, or to limit graph reparation when nodes are removed.

    • reparationBacklinkProbability: when a node is removed, its neighborhood is repaired. Use this to configure the probability of adding backlinks between repaired nodes (defaults to 1.0, which is always).

    • vectorCacheHintSizeKB: a non-binding hint for the maximum size of the vector cache (default: 2 GB). Note: memory is only allocated for caching as needed. E.g. smaller data sets will reserve less memory.

    Insert Vector Objects

    Vector objects are inserted like any other data objects in ObjectBox (the indexing is done automatically behind the scenes):

    In Python, vector values can be plain Python lists or numpy arrays (the property type must be compatible with numpy array dtype).

    Performance note: for inserting multiple objects at once, wrap a around the put commands.

    Note: for the City example, it is easy to obtain the vector values. For more complex use cases it usually takes an additional step to create the vectors. Often an AI model is used, which is covered in a section further down.

    Perform a nearest neighbor search

    (Approximate) nearest neighbor search is part of the standard . For vector properties with an HNSW index, a special "nearest neighbor" query condition is available when building a query. It accepts a query vector to search neighbors for, and the maximum number of results to return. See the method documentation for additional details.

    Once the query is built, one of its "find" methods is called to execute it. For vector search, a special set of "find with scores" methods is available, that also return the distance (the "score") to the queried vector. Additionally, only the "find with scores" methods will return the results ordered by the distance (nearest first). And as with other find methods, you can either only retrieve result IDs or complete objects.

    And that's it! You have successfully performed your first vector search with ObjectBox. 🎉

    Note: if results do not need to be in any particular order, use the standard find methods that do not come with scores. Performance-wise they may have a tiny advantage, but again, do not expect the nearest elements to be the first results.

    AI Embeddings

    Vector search is a central component in AI applications. In this context you will often be confronted with the term "embeddings". So, let's have a closer look:

    To make complex data (e.g. texts, images, videos, ...) accessible, it is transformed into an N-dimensional vector representation. This is called an embedding. An embedding has a certain meaning (or semantics) based on a given model and is associated with its source document. For example with multi-modal models, the word "cat" would be transformed to a vector that is nearby a vector that originated from a picture of a cat. This is the power of embeddings: they capture the essence of the data they represent. For AI Large Language Models (LLMs) these vector embeddings also mimic some "understanding" and semantics become "computable".

    Note that ObjectBox works with vectors, so it is your responsibility to create these vectors. E.g. you need access to some model that transforms data into vectors (embeddings). These models can be online or offline.

    Typically, you will also use the model to transform the query. Let's say you have associated all your pictures with a vector by a multi-modal model and stored all vectors into ObjectBox. Now using the same model, you create a search vector to find similar pictures. The input to get this vector can be a single word like "cat", a sentence like "birthday party with friends", or an image showing a specific car.

    RAG

    To take the previous section on embeddings a bit further, let's enter Retrieval Augmented Generation (RAG). RAG allows interactions between an LLM and external sources to produce better and individual results. Vector databases like ObjectBox are extremely efficient in that regard as they speak the "native language" of LLMs: vector embeddings. In contrast, a standard web search is magnitudes slower. In short, you can make individual data available in ObjectBox and wire it to an LLM to make this knowledge available. This wiring is already done by various libraries such as LangChain.

    LangChain

    For Python, we offer the package "langchain-objectbox" that integrates ObjectBox into the LangChain framework:

    Once installed, you can use ObjectBox as a local on-device vector store. It implements VectorStore and thus you can instantiate it with e.g. from_documents or from_texts or there asynchronous variants. Example:

    Current Limitations and Future Plans

    Current limitations:

    • Python APIs are non-final and subject to change

    Expect more features to come:

    • Releases for all programming languages supported by ObjectBox

    • Closer integration into queries to prefilter objects

    • Performance improvements, e.g. on ARM CPUs

    • Additional distance functions

    Vector Search FAQ

    How does this compare to libraries like FAISS?

    ObjectBox Vector Search offers significant advantages by combining the capabilities of a vector search engine with the robustness of a full-featured database:

    1. No Memory Limitations

      • Unlike in-memory libraries like FAISS, ObjectBox uses disk storage when data exceeds available memory, enabling scalability.

      • Smart caching ensures frequently accessed data remains in memory for optimal performance.

    2. Instant Readiness

    With ObjectBox Vector Search, you get a powerful, flexible, and scalable solution tailored for modern applications where data relationships matter.

    If you are interested in scientific details, check out the HNSW paper.

  • indexingSearchCount (aka "efConstruction" in HNSW terms): the number of neighbors searched for while indexing (default: 100). The default value serves as a starting point that can likely be optimized for specific datasets and use cases. The higher the value, the more accurate the search, but the longer the indexing will take. If indexing time is not a major concern, a value of at least 200 is recommended to improve search quality.

  • Quantization and support for non-float vectors

  • There's no need for an initial data load when starting the application. Simply open the database, and you can immediately begin performing vector searches.

  • Efficient Updates

    • When data changes (e.g., adding, modifying, or deleting entries), ObjectBox only persists the changes (deltas).

    • In contrast, most libraries rewrite the entire dataset, if they even support persistent storage.

  • ACID Transactions

    • ObjectBox ensures all updates are safely persisted, offering robust data integrity.

    • This eliminates concerns about data corruption during unexpected events, such as power outages.

  • Unified Data Storage

    • Vector data is rarely standalone; it often ties to related objects or metadata.

    • ObjectBox enables you to store your entire data model, keeping all related data in one place. For example:

      • An embedding model can generate vectors for images. These vectors can be stored alongside related properties like creation date, tags, filenames, etc.

      • Additionally, associated objects like users (e.g., the creator or those who liked the image) can also be stored within the same database.

  • curse of dimensionality
    Getting started
    getting started guide
    transaction
    ObjectBox Query API
    cityBox.put({
                 City{0, "Barcelona", {41.385063F, 2.173404F}},
                 City{0, "Nairobi", {-1.292066F, 36.821945F}},
                 City{0, "Salzburg", {47.809490F, 13.055010F}}
    });
    @Entity()
    class City:
        id = Id
        name = String
        location = Float32Vector(index=HnswIndex(
            dimensions=2,
            distance_type=VectorDistanceType.EUCLIDEAN
        ))
    @Entity()
    class City {
      @Id()
      int id = 0;
    
      String? name;
    
      @HnswIndex(dimensions: 2, distanceType: VectorDistanceType.geo)
      @Property(type: PropertyType.floatVector)
      List<double>? location;
      
      City(this.name, this.location);
    }
    @Entity
    public class City {
        @Id 
        long id = 0;
    
        @Nullable 
        String name;
    
        @HnswIndex(dimensions = 2, distanceType = VectorDistanceType.GEO)
        float[] location;
        
        public City(@Nullable String name, float[] location) {
            this.name = name;
            this.location = location;
        }
    }
    @Entity
    data class City(
        @Id var id: Long = 0,
        var name: String? = null,
        @HnswIndex(dimensions = 2, distanceType = VectorDistanceType.GEO) 
        var location: FloatArray? = null
    )
    // objectbox: entity
    class City {
        var id: Id = 0
        
        var name: String?
        
        // objectbox:hnswIndex: dimensions=2, distanceType="geo"
        var location: [Float]?    
    }
    
    // The syntax for all supported options is:
    // objectbox:hnswIndex: dimensions=2, neighborsPerNode=30, indexingSearchCount=100, flags="debugLogs", distanceType="euclidean", reparationBacklinkProbability=0.95, vectorCacheHintSizeKB=2097152
    
    // flags may be a comma-separated list of debugLogs, debugLogsDetailed, reparationLimitCandidates, vectorCacheSimdPaddingOff
    // distanceType may be one of euclidean, geo, cosine, dotProduct, dotProductNonNormalized
    city.fbs
    table City {
        id: ulong;
        name: string;
        /// objectbox: index=hnsw, hnsw-dimensions=2
        /// objectbox: hnsw-distance-type=Euclidean
        location: [float];
    }
    
    
    struct City {
        obx_id id;
        std::string name;
        std::vector<float> location;
    }
    store = Store()
    box = store.box(City)
    box.put(City(name="Barcelona", location=[41.385063, 2.173404]))
    box.put(City(name="Nairobi", location=[-1.292066, 36.821945]))
    box.put(City(name="Salzburg", location=np.array([47.809490, 13.055010])))
    final box = store.box<City>();
    box.putMany([
      City("Barcelona", [41.385063, 2.173404]),
      City("Nairobi", [-1.292066, 36.821945]),
      City("Salzburg", [47.809490, 13.055010]),
    ]);
    final Box<City> box = store.boxFor(City.class);
    box.put(
            new City("Barcelona", new float[]{41.385063F, 2.173404F}),
            new City("Nairobi", new float[]{-1.292066F, 36.821945F}),
            new City("Salzburg", new float[]{47.809490F, 13.055010F})
    );
    val box = store.boxFor(City::class)
    box.put(
        City(name = "Barcelona", location = floatArrayOf(41.385063f, 2.173404f)),
        City(name = "Nairobi", location = floatArrayOf(-1.292066f, 36.821945f)),
        City(name = "Salzburg", location = floatArrayOf(47.809490f, 13.055010f))
    )
    let box: Box<City> = store.box()
    try box.put([
      City("Barcelona", [41.385063, 2.173404]),
      City("Nairobi", [-1.292066, 36.821945]),
      City("Salzburg", [47.809490, 13.055010]),
    ])
    # Query vector
    madrid = [40.416775, -3.703790]  
    
    # Prepare a Query object to search for the 2 closest neighbors:
    query = box.query(City.location.nearest_neighbor(madrid, 2)).build()
    
    # Retrieve IDs
    results = query.find_ids_with_scores()
    for id_, score in results:
        print(f"City ID: {id_}, distance: {score}")
    
    # Retrieve objects
    results = query.find_with_scores()
    for object_, score in results: 
        print(f"City: {object_.name}, distance: {score}")
    final madrid = [40.416775, -3.703790]; // query vector
    // Prepare a Query object to search for the 2 closest neighbors:
    final query = box
        .query(City_.location.nearestNeighborsF32(madrid, 2))
        .build();
    
    // Combine with other conditions as usual
    final query = box
        .query(City_.location.nearestNeighborsF32(madrid, 2)
        .and(City_.name.startsWith("B")))
        .build();
        
    // Retrieve IDs
    final results = query.findIdsWithScores();
    for (final result in results) {
      print("City ID: ${result.id}, distance: ${result.score}");
    }
    
    // Retrieve objects
    final results = query.findWithScores();
    for (final result in results) {
      print("City: ${result.object.name}, distance: ${result.score}");
    }
    final float[] madrid = {40.416775F, -3.703790F}; // query vector
    // Prepare a Query object to search for the 2 closest neighbors:
    final Query<City> query = box
            .query(City_.location.nearestNeighbors(madrid, 2))
            .build();
    
    // Combine with other conditions as usual
    final Query<City> query = box
            .query(City_.location.nearestNeighbors(madrid, 2)
                    .and(City_.name.startsWith("B")))
            .build();
    
    // Retrieve IDs
    final List<IdWithScore> results = query.findIdsWithScores();
    for (IdWithScore result : results) {
        System.out.printf("City ID: %d, distance: %f%n", result.getId(), result.getScore());
    }
    
    // Retrieve objects
    final List<ObjectWithScore<City>> results = query.findWithScores();
    for (ObjectWithScore<City> result : results) {
        System.out.printf("City: %s, distance: %f%n", result.get().name, result.getScore());
    }
    val madrid = floatArrayOf(40.416775f, -3.703790f) // query vector
    // Prepare a Query object to search for the 2 closest neighbors:
    val query = box
        .query(City_.location.nearestNeighbors(madrid, 2))
        .build()
    
    // Combine with other conditions as usual
    val query = box
        .query(
            City_.location.nearestNeighbors(madrid, 2)
                .and(City_.name.startsWith("B"))
        )
        .build()
    
    // Retrieve IDs
    val results = query.findIdsWithScores()
    for (result in results) {
        println("City ID: ${result.id}, distance: ${result.score}")
    }
    
    // Retrieve objects
    val results = query.findWithScores()
    for (result in results) {
        println("City: ${result.get().id}, distance: ${result.score}")
    }
    let madrid = [40.416775, -3.703790] // query vector
    // Prepare a Query object to search for the 2 closest neighbors:
    let query = try box
        .query { City.location.nearestNeighbors(queryVector: madrid, maxCount: 2) }
        .build()
    
    // Combine with other conditions as usual
    final query = box
        .query { City.location.nearestNeighbors(queryVector: madrid, maxCount: 2)
                 && City.name.startsWith("B") }
        .build()
        
    // Retrieve IDs
    let results = try query.findIdsWithScores()
    for result in results {
        print("City ID: \(result.id), distance: \(result.score)")
    }
    
    // Retrieve objects
    let results = try query.findWithScores()
    for result in results {
        print("City: \(result.object.name), distance: \(result.score)")
    }
    float madrid[] = {40.416775f, -3.703790f};
    obx::Query<City> query = cityBox
            .query(City_::location.nearestNeighbors(madrid, 1))
            .build();
    std::vector<std::pair<City, double>> citiesWithScores =
        queryCityByLocation_.findWithScores();
    
    // Print the results
    printf("%3s  %-18s  %-19s %-10s\n", "ID", "Name", "Location", "Score");
    for (const auto& pair : citiesWithScores) {
        const City& city = pair.first;
        printf("%3" PRIu64 "  %-18s  %-9.2f %-9.2f %5.2f\n", city.id, city.name.c_str(),
               city.location[0], city.location[1], pair.second);
    }
    pip install langchain-objectbox --upgrade
    from langchain_objectbox.vectorstores import ObjectBox
    
    objectbox = ObjectBox.from_texts(texts, embeddings,
           embedding_dimensions=768)

    Getting started

    Discover ObjectBox: The Lightning-Fast Mobile Database for Persistent Object Storage. Streamline Your Workflow, Eliminate Repetitive Tasks, and Enjoy a User-Friendly Data Interface.

    Add ObjectBox to your project

    Prefer to look at example code? Check out our examples repository.

    ObjectBox tools and dependencies are available on .

    To add ObjectBox to your Android project, follow these steps:

    1. Open the Gradle build file of your root project (not the ones for your app or module subprojects) and add a global variable for the version and the ObjectBox Gradle plugin:

    1. Open the Gradle build file for your app or module subproject and, after the com.android.application plugin, apply the io.objectbox plugin:

    If you encounter any problems in this or later steps, check out the and pages.

    1. Then do "Sync Project with Gradle Files" in Android Studio so the Gradle plugin automatically adds the required ObjectBox libraries and code generation tasks.

    2. Your project can now use ObjectBox, continue by defining entity classes.

    Prefer to look at example code? Check out .

    The ObjectBox Java SDK and runtime libraries support applications:

    • running on the JVM on Linux (x86_64, arm64, armv7), Windows (x86_64) and macOS 10.15 or newer (x86_64, Apple M1)

    • written in Java or Kotlin

    You can watch these video tutorials as well 😀:

    Prefer to look at example code? Check out our .

    1. Run these commands:

    1. This should add lines like this to your pubspec.yaml:

    Prefer to look at example code? Check out our .

    ObjectBox for Python is available via PyPI: Stable Version (4.0.0):

    Define Entity Classes

    Define your data model by creating a class with at least an ID property, a so called entity.

    A simple entity representing a user with an ID and a name property could look like this:

    When using a data class, add default values for all parameters. This will ensure your data class will have a constructor that can be called by ObjectBox. (Technically this is only required if adding properties to the class body, like custom or transient properties or relations, but it's a good idea to do it always.)

    Avoid naming properties like reserved Java keywords, like

    Important:

    • Entities must have exactly one 64-bit integer ID property (a Java long, Kotlin Long, Dart int). If you need another type for the ID, like a string, for some tips. Also, the ID property must have non-private visibility (or non-private getter and setter methods).

    • Entities must also have a no-argument constructor, or for better performance, a constructor with all properties as arguments. In the above examples, a default, no-argument constructor is generated by the compiler.

    is already built-in, but almost any type can be stored .

    For more details about entities, like how to create an index or a relation, check the page.

    You can also .

    ObjectBox also supports changing your model at a later point. You can add and remove properties in entities and the database model is updated automatically (after re-generating some code, see section below). There is no need to write migration code.

    To rename entities or properties, change the type of a property and more details in general see .

    Generate ObjectBox code

    Next, we generate some binding code based on the model defined in the previous step.

    Build your project to generate the MyObjectBox class and other classes required to use ObjectBox, for example using Build > Make Project in Android Studio.

    Note: If you make significant changes to your entities, e.g. by moving them or modifying annotations, make sure to rebuild the project so generated ObjectBox code is updated.

    To change the package of the MyObjectBox class, see the annotation processor options on the page.

    Among other files ObjectBox generates a JSON model file, by default to

    • app/objectbox-models/default.json for Android projects,

    • lib/objectbox-model.json for Dart/Flutter projects, or

    • <user-module-dir>/objectbox-model.json for Python projects

    To change the model file path, see .

    In Android Studio you might have to switch the Project view from Android to Project to see the default.json model file. Python checks for the call-stack to determine the user-module directory in which the JSON file is stored.

    This JSON file changes when you change your entity classes (or sometimes with a new version of ObjectBox).

    Keep this JSON file, commit the changes to version control!

    This file keeps track of unique IDs assigned to your entities and properties. This ensures that an older version of your database can be smoothly upgraded if your entities or properties change.

    The model file also enables you to keep data or to when two of your developers make changes at the same time.

    Create a Store

    (Java) or (Dart) is the entry point for using ObjectBox. It is the direct interface to the database and manages Boxes. Typically, you want to only have a single Store (single database) and keep it open while your app is running, not closing it explicitly.

    Create it using the builder returned by the generated MyObjectBox class, for example in a small helper class like this:

    If you encounter UnsatisfiedLinkError or LinkageError on the build call, see for solutions.

    The best time to initialize ObjectBox is when your app starts. We suggest to do it in the onCreate method of your :

    It is possible to specify various options when building a store. Notably for testing or caching, to use an in-memory database that does not create any files:

    For more store configuration options: for Java see the and for Dart the documentation. (Python APIs will be published soon)

    Basic Box operations

    The is likely the class you interact with most. A Box instance gives you access to objects of a particular type. For example, if you have User and Order entities, you need a Box object to interact with each:

    These are some of the operations offered by the Box class:

    put inserts a new object or updates an existing one (with the same ID). When inserting, an ID will be assigned to the just inserted object (this will be explained below) and returned. put also supports putting multiple objects, which is more efficient.

    get and getAll: Given an object’s ID, get reads it from its box. To get all objects in the box use getAll .

    query: Starts building a query to return objects from the box that match certain conditions. See for details.

    remove and removeAll: Remove a previously put object from its box (deletes it). remove also supports removing multiple objects, which is more efficient. removeAll removes (deletes) all objects in a box.

    count: Returns the number of objects stored in this box.

    For a complete list of methods available in the Box class, check the API reference documentation for or .

    Asynchronous operations

    ObjectBox has built-in support to run (typically multiple or larger) database operations asynchronously.

    runInTxAsync and callInTxAsync: runs the given Runnable/Callable in a transaction on a background thread (the internal ObjectBox thread pool) and calls the given callback once done. In case of callInTxAsync the callback also receives the returned result.

    awaitCallInTx (Kotlin Coroutines only): wraps callInTxAsync in a coroutine that suspends until the transaction has completed. Likewise, on success the return value of the given callable is returned, on failure an exception is thrown.

    Most Box methods do have async versions which run the operation in a worker isolate.

    For example putAsync: asynchronously inserts a new object or updates an existing one (with the same ID). The returned future completes when the object is successfully written to the database.

    To run multiple operations, it is more efficient to wrap the synchronous calls in an asynchronous transaction with runInTransactionAsync (): run a callback with multiple database operations within a write or read transaction in the background without blocking the user interface. Can return results.

    Object IDs

    By default IDs for new objects are assigned by ObjectBox. When a new object is put, it will be assigned the next highest available ID:

    For example, if there is an object with ID 1 and another with ID 100 in a box, the next new object that is put will be assigned ID 101.

    If you try to assign a new ID yourself and put the object, ObjectBox will throw an error.

    If you need to assign IDs by yourself, have a look at and what side effects apply.

    Reserved Object IDs

    Object IDs can not be:

    • 0 (zero) or null (if using java.lang.Long) As said above, when putting an object with ID zero it will be assigned an unused ID (not zero).

    • 0xFFFFFFFFFFFFFFFF (-1 in Java) Reserved for internal use.

    For a detailed explanation see the page on .

    Transactions

    While ObjectBox offers powerful transactions, it is sufficient for many apps to consider just some basics guidelines about transactions:

    • A put runs an implicit transaction.

    • Prefer put bulk overloads for lists (like put(entities)) when possible.

    • For a high number of DB interactions in loops, consider explicit transactions, such as using runInTx()

    For more details check the separate .

    Have an app with greenDAO? DaoCompat is for you!

    DaoCompat is a compatibility layer that gives you a greenDAO like API for ObjectBox. It makes switching from greenDAO to ObjectBox simple. Have a look at and . if you have any questions!

    Next steps

    • Check out the .

    • Learn about and .

    • Learn .

    • To enable debug mode and for advanced use cases, see the page.

    targeting at least Java 8

  • built with Gradle or Maven

  • ObjectBox tools and dependencies are available on the Maven Central repository.

    Maven projects

    To set up a Maven project, see the README of the Java Maven example project.

    Gradle projects

    The instructions assume a multi-project build is used.

    1. Open the Gradle build script of your root project and

      1. add a global variable to store the common version of ObjectBox dependencies and

      2. add the ObjectBox Gradle plugin:

    1. Open the Gradle build file for your application subproject and, after other plugins, apply the io.objectbox plugin:

    Using your IDE of choice with a Gradle project might require additional configuration. E.g.

    • For IntelliJ IDEA see the help page for Gradle.

    • For Eclipse see the Buildship project and Getting Started article.

    1. Optionally, add a runtime library for each platform that your application should run on and instead apply the Gradle plugin after the dependencies block:

    The ObjectBox database runs mostly in native code written in C/C++ for optimal performance. Thus, ObjectBox will load a runtime library: a “.dll” on Windows, a “.so” on Linux, and a “.dylib” on macOS.\

    By default, the Gradle plugin adds a runtime library (only) for your current operating system. It also adds the Java SDK (objectbox-java) and if needed the ObjectBox Kotlin extension functions (objectbox-kotlin).

    ObjectBox only supports 64-bit systems for best performance going forward. Talk to us if you require 32-bit support.

    1. Your project can now use ObjectBox, continue by defining entity classes.

    Task-list app (in Spanish)

    Prefer to look at example code? Check out our examples directory.

    To add ObjectBox to your Flutter project:

    1. Run these commands:

    Or to use ObjectBox Sync (requires access to the Sync feature) instead run:

    To run unit tests on your machine, download the latest native ObjectBox library for your machine by running this script in a bash shell (e.g. Git Bash on Windows):

    bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-dart/main/install.sh)

    To get a variant of the library that supports ObjectBox Sync, append the --sync argument to above command.

    1. This should add lines like this to your pubspec.yaml:

    1. If you added the above lines manually, then install the packages with flutter pub get.

    For all iOS apps target iOS 15.0: in ios/Podfile change the platform and in the ios/Runner.xcodeproj/poject.pbxproj file update IPHONEOS_DEPLOYMENT_TARGET (or open the Runner workspace in Xcode and edit the build setting). In ios/Flutter/AppframeworkInfo.plist update MinimumOSVersion to 15.0.

    For all macOS apps target macOS 11.0: in macos/Podfile change the platform and in the macos/Runner.xcodeproj/poject.pbxproj file update MACOSX_DEPLOYMENT_TARGET (or open the Runner workspace in Xcode and edit the build setting).

    For macOS apps using Sync, open macos/Runner.xcodeproj in Xcode and for the Runner target under Signing & Capabilities in the App Sandbox sections, enable incoming and outgoing network access.

    For Linux Desktop apps: the Flutter snap ships with an outdated version of CMake. Install Flutter manually instead to use the version of CMake installed on your system.

  • If you added the above lines manually, then install the packages with dart pub get

  • Install the ObjectBox C library for your system (on Windows you can use "Git Bash"):

  • Or to use ObjectBox Sync (requires access to the Sync feature) instead run:

    By default the library is downloaded into the lib subdirectory of the working directory. It's not necessary to install the library system-wide. This also allows to use different versions for different projects. For details see below.

    Deploying Dart Native projects

    Natively compiled Dart applications that use ObjectBox Dart require a reference to the objectbox-c library. Hence, the shared library file downloaded with install.sh needs to be shipped with the executable.

    The install.sh script downloads the library by default to the lib subdirectory of the working directory. An executable using ObjectBox Dart looks for the library in this lib directory.

    If it is not found there, it falls back to using system directories (using Dart's DynamicLibrary.open):

    • Windows: working directory and %WINDIR%\system32.

    • macOS: /usr/local/lib (and maybe others).

    • Linux: /lib and /usr/lib (again, possibly others).

    private
    and
    default
    .
    ObjectBox tooling works with the Java representation of your Kotlin code to be compatible with both Java and Kotlin. It will ignore such properties.

    You can have multiple entities in the same file (here models.dart), or you can have them spread across multiple files in your package's lib directory.

    To generate the binding code required to use ObjectBox run

    dart run build_runner build

    ObjectBox generator will look for all @Entity annotations in your lib folder and create

    • a single database definition lib/objectbox-model.json and

    • supporting code in lib/objectbox.g.dart.

    To customize the directory where generated files are written see Advanced Setup.

    If you make changes to your entities, e.g. by adding a property or modifying annotations, or after the ObjectBox library has updated make sure to re-run the generator so generated ObjectBox code is updated.

    You typically commit the generated code file objectbox.g.dart to your version control system (e.g. git) to avoid having to re-run the generator unless there are changes.

    Actually we lied above. The generator will process lib and test folders separately and generate files for each one (if @Entity classes exist there). This allows to create a separate test database that does not share any of the entity classes with the main database.

    Python bindings offer a convenient default Model to which Entity definitions are automatically associated if not specified otherwise. Similar to the other bindings, a JSON model file is also used for management of Schema history (i.e. to handle add/remove/rename of Entity and Property).

    Create it using the builder returned by the generated MyObjectBox class, for example in a small helper class like this:

    If you encounter UnsatisfiedLinkError or LinkageError on the build call, see App Bundle, split APKs and Multidex for solutions.

    The best time to initialize ObjectBox is when your app starts. We suggest to do it in the onCreate method of your Application class:

    The best time to initialize ObjectBox is when your app starts. For a command line app this is typically inside the main method.

    Create it using the generated openStore() method, for example in a small helper class like this:

    For sandboxed macOS apps also pass macosApplicationGroup to openStore(). See the notes about "macOS application group" in the constructor documentation of the Store class.

    For example:

    openStore(macosApplicationGroup: "FGDTDLOBXDJ.demo")

    On mobile devices or sandboxed apps data should be stored in the app's documents directory. See for more info. This is exactly what openStore()does, if the directory argument is not specified.

    On desktop systems it is recommended to specify a directory to create a custom sub-directory to avoid conflicts with other apps.

    If your code passes a directory that the application can't write to, you get an error that looks somewhat like this: failed to create store: 10199 Dir does not exist: objectbox (30)

    The best time to initialize ObjectBox is when your app starts. We suggest to do it in your app's main() function:

    When using Dart isolates, note that , they do not share state on the Dart level.

    However, as ObjectBox runs on the native or process level (so one native instance shared across all isolates), instead of creating a new Store in another isolate your code should instead .

    Create it using the generated openStore() method, for example like this:

    The above minimal example omits the argument to (directory: ), using the default - ./objectbox - in the current working directory.

    When using Dart isolates, note that each Dart isolate has its own global fields, they do not share state on the Dart level.

    However, as ObjectBox runs on the native or process level (so one native instance shared across all isolates), instead of creating a new Store in another isolate your code should instead attach to the open native store.

    There is also runAsync (API reference): like runInTransactionAsync but does not start a transaction, leaving that to your callback code. This allows to supply a callback that is an async function.

    If it is necessary to call put many times in a row, take a look at putQueued: Schedules the given object to be put later on, by an asynchronous queue, returns the id immediately even though the object may not have been written yet. You can use Store's awaitQueueCompletion() or awaitQueueSubmitted() to wait for the async queue to finish.

    Currently work in progress.

    .
    the Maven Central repository
    FAQ
    Troubleshooting
    our examples repository
    Event Management app
    Restaurant: chef and order apps
    examples directory
    examples directory
    see the @Id annotation docs
    Support for many property types
    with a converter
    Entity Annotations
    learn more about the ObjectBox model
    Data Model Updates
    Advanced Setup
    Advanced Setup
    when renaming entities or properties
    resolve conflicts
    BoxStore
    Store
    App Bundle, split APKs and Multidex
    Application class
    BoxStoreBuilder
    Store
    Box class
    queries
    Java
    Dart
    API reference
    how to switch to self-assigned IDs
    Object IDs
    transaction documentation
    the documentation
    the example
    Contact us
    ObjectBox example projects on GitHub
    Queries
    Relations
    how to write unit tests
    Advanced Setup
    models.dart
    @Entity()
    class User {
      @Id()
      int id = 0;
      
      String? name;
    }
    model.py
    from objectbox import Entity, Id, String
    
    @Entity()
    class User:
      id = Id
      name = String
      
    object ObjectBox {
        lateinit var store: BoxStore
            private set
    
        fun init(context: Context) {
            store = MyObjectBox.builder()
                    .androidContext(context)
                    .build()
        }
    }
    class ExampleApp : Application() {
        override fun onCreate() {
            super.onCreate()
            ObjectBox.init(this)
        }
    }
    public class ObjectBox {
        private static BoxStore store;
    
        public static void init(Context context) {
            store = MyObjectBox.builder()
                    .name("objectbox-notes-db")
                    .build();
        }
    
        public static BoxStore get() { return store; }
    }
    import 'package:path/path.dart' as p;
    import 'package:path_provider/path_provider.dart';
    import 'objectbox.g.dart'; // created by `flutter pub run build_runner build`
    
    class ObjectBox {
      /// The Store of this app.
      late final Store store;
      
      ObjectBox._create(this.store) {
        // Add any additional setup code, e.g. build queries.
      }
    
      /// Create an instance of ObjectBox to use throughout the app.
      static Future<ObjectBox> create() async {
        final docsDir = await getApplicationDocumentsDirectory();
        // Future<Store> openStore() {...} is defined in the generated objectbox.g.dart
        final store = await openStore(directory: p.join(docsDir.path, "obx-example"));
        return ObjectBox._create(store);
      }
    }
    import 'objectbox.g.dart'; // created by `dart pub run build_runner build`
    
    void main() {
      // Store openStore() {...} is defined in the generated objectbox.g.dart
      final store = openStore();
    
      // your app code ...
    
      store.close(); // don't forget to close the store
    }
    from objectbox import Store
      
    store = Store()
    /build.gradle(.kts)
    buildscript {
        ext.objectboxVersion = "5.0.1" // For Groovy build scripts
        // val objectboxVersion by extra("5.0.1") // For KTS build scripts
        
        repositories {
            mavenCentral()
        }
        
        dependencies {
            // Android Gradle Plugin 8.0 or later supported
            classpath("com.android.tools.build:gradle:8.0.2")
            classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion")
        }
    }
    /app/build.gradle(.kts)
    // Using plugins syntax:
    plugins {
        id("com.android.application")
        id("kotlin-android") // Only for Kotlin projects
        id("kotlin-kapt") // Only for Kotlin projects
        id("io.objectbox") // Apply last
    }
    
    // Or using the old apply syntax:
    apply plugin: "com.android.application"
    apply plugin: "kotlin-android" // Only for Kotlin projects
    apply plugin: "kotlin-kapt" // Only for Kotlin projects
    apply plugin: "io.objectbox" // Apply last
    dart pub add objectbox
    dart pub add --dev build_runner objectbox_generator:any
    dependencies:
      objectbox: ^5.0.4
    
    dev_dependencies:
      build_runner: ^2.4.11
      objectbox_generator: any
    pip install --upgrade objectbox
    User.java
    @Entity
    public class User {
        @Id 
        public long id;
        public String name;
    }
    models.kt
    @Entity
    data class User(
            @Id 
            var id: Long = 0,
            var name: String? = null
    )
    public class ObjectBox {
        private static BoxStore store;
    
        public static void init(Context context) {
            store = MyObjectBox.builder()
                    .androidContext(context)
                    .build();
        }
    
        public static BoxStore get() { return store; }
    }
    public class ExampleApp extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            ObjectBox.init(this);
        }
    }
    BoxStore inMemoryStore = MyObjectBox.builder()
            .androidContext(context)
            .inMemory("test-db")
            .build();
     final inMemoryStore =
         Store(getObjectBoxModel(), directory: "memory:test-db");
    store = Store(directory="memory:testdata")
    Box<User> userBox = store.boxFor(User.class);
    Box<Order> orderBox = store.boxFor(Order.class);
    val userBox = store.boxFor(User::class)
    val orderBox = store.boxFor(Order::class)
    final userBox = store.box<User>();
    final orderBox = store.box<Order>();
    user_box = store.box(User)
    order_box = store.box(Order)
    User user = new User("Tina");
    userBox.put(user);
    
    List<User> users = getNewUsers();
    userBox.put(users);
    val user = User(name = "Tina")
    userBox.put(user)
    
    val users: List<User> = getNewUsers()
    userBox.put(users)
    final user = User(name: 'Tina');
    userBox.put(user);
    
    final users = getNewUsers();
    userBox.putMany(users);
    user = User(name="Tina")
    user_box.put(user)
    
    users = get_new_users()
    user_box.put(*users)
    User user = userBox.get(userId);
    
    List<User> users = userBox.getAll();
    val user = userBox[userId]
    
    val users = userBox.all
    final user = userBox.get(userId);
    
    final users = userBox.getMany(userIds);
    
    final users = userBox.getAll();
    user = user_box.get(user_id)
    
    users = user_box.get_all()
    Query<User> query = userBox
        .query(User_.name.equal("Tom"))
        .order(User_.name)
        .build();
    List<User> results = query.find();
    query.close();
    val query = userBox
        .query(User_.name.equal("Tom"))
        .order(User_.name)
        .build()
    val results = query.find()
    query.close()
    final query =
        (userBox.query(User_.name.equals('Tom'))..order(User_.name)).build();
    final results = query.find();
    query.close();
    query = user_box \
        .query(User.name.equals('Tom')) \
        .build()
    results = query.find()
    boolean isRemoved = userBox.remove(userId);
    
    userBox.remove(users);
    // alternatively:
    userBox.removeByIds(userIds);
    
    userBox.removeAll();
    val isRemoved = userBox.remove(userId)
    
    userBox.remove(users)
    // alternatively:
    userBox.removeByIds(userIds)
    
    userBox.removeAll()
    final isRemoved = userBox.remove(userId);
    
    userBox.removeMany(userIds);
    
    userBox.removeAll();
    is_removed = user_box.remove(user_id)
    
    user_box.remove_all()
    long userCount = userBox.count();
    val userCount = userBox.count()
    final userCount = userBox.count();
    user_box.count()
    store.callInTxAsync(() -> {
        Box<User> box = store.boxFor(User.class);
        String name = box.get(userId).name;
        box.remove(userId);
        return text;
    }, (result, error) -> {
        if (error != null) {
            System.out.println("Failed to remove user with id " + userId);
        } else {
            System.out.println("Removed user with name: " + result);
        }
    });
    try {
        val name = store.awaitCallInTx {
            val box = store.boxFor(User::class.java)
            val name = box.get(userId).name
            box.remove(userId)
            name
        }
        println("Removed user with name $name")
    } catch (e: Exception) {
        println("Failed to remove user with id $userId")
    }
    final user = User(name: 'Tina');
    Future<int> idFuture = userBox.putAsync(user);
    
    ...
    
    final id = await idFuture;
    userBox.get(id); // after the future completed, the object is inserted
    User user = new User();
    // user.id == 0
    box.put(user);
    // user.id != 0
    long id = user.id;
    val user = User()
    // user.id == 0
    box.put(user)
    // user.id != 0
    val id = user.id
    final user = User();
    // user.id == 0
    box.put(user);
    // user.id != 0
    final id = user.id;
    user = User()
    box.put(user)
    id: int = user.id
    /build.gradle(.kts)
    buildscript {
        ext.objectboxVersion = "5.0.1" // For Groovy build scripts
        // val objectboxVersion by extra("5.0.1") // For KTS build scripts
        
        repositories {
            mavenCentral()
        }
        
        dependencies {
            classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion")
        }
    }
    /app/build.gradle(.kts)
    // Using plugins syntax:
    plugins {
        id("java-library") // or org.jetbrains.kotlin.jvm for Kotlin projects.
        id("io.objectbox") // Apply last.
    }
    
    // Or using the old apply syntax:
    apply plugin: "java-library" // or org.jetbrains.kotlin.jvm for Kotlin projects.
    apply plugin: "io.objectbox" // Apply last.
    dependencies {
        // ObjectBox platform-specific runtime libraries
        // Add or remove them as needed to match what your application supports
        // Linux (x64)
        implementation("io.objectbox:objectbox-linux:$objectboxVersion")
        // macOS (Intel and Apple Silicon)
        implementation("io.objectbox:objectbox-macos:$objectboxVersion")
        // Windows (x64)
        implementation("io.objectbox:objectbox-windows:$objectboxVersion")
    
        // Additional ObjectBox runtime libraries
        // Linux (32-bit ARM)
        implementation("io.objectbox:objectbox-linux-arm64:$objectboxVersion")       
        // Linux (64-bit ARM)
        implementation("io.objectbox:objectbox-linux-armv7:$objectboxVersion")
    }
    
    // When manually adding ObjectBox dependencies, the plugin must be
    // applied after the dependencies block so it can detect them.
    // Using Groovy build scripts
    apply plugin: "io.objectbox"
    // Using KTS build scripts
    apply(plugin = "io.objectbox")
    flutter pub add objectbox objectbox_flutter_libs:any
    flutter pub add --dev build_runner objectbox_generator:any
    flutter pub add objectbox objectbox_sync_flutter_libs:any
    flutter pub add --dev build_runner objectbox_generator:any
    dependencies:
      objectbox: ^5.0.4
      objectbox_flutter_libs: any
      # If you run the command for ObjectBox Sync it should add instead:
      # objectbox_sync_flutter_libs: any
    
    dev_dependencies:
      build_runner: ^2.4.11
      objectbox_generator: any
    bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-dart/main/install.sh)
    bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-dart/main/install.sh) --sync
    // The callback must be a function that can be sent to an isolate: 
    // either a top-level function, static method or a closure that only
    // captures objects that can be sent to an isolate.
    String? readNameAndRemove(Store store, int objectId) {
      var box = store.box<User>();
      final nameOrNull = box.get(objectId)?.name;
      box.remove(objectId);
      return nameOrNull;
    }
    final nameOrNull = 
      await store.runInTransactionAsync(TxMode.write, readNameAndRemove, objectId);
    for (int i = 0; i < 100; i++) {
      userBox.putQueued(User(name: 'User $i'));
    }
    
    // Optional: wait until submitted items are processed.
    store.awaitQueueSubmitted();
    expect(userBox.count(), equals(100));
    .
    Flutter: read & write files
    each Dart isolate has its own global fields
    attach to the open native store
    /// Provides access to the ObjectBox Store throughout the app.
    late ObjectBox objectbox;
    
    Future<void> main() async {
      // This is required so ObjectBox can get the application directory
      // to store the database in.
      WidgetsFlutterBinding.ensureInitialized();
    
      objectbox = await ObjectBox.create();
    
      runApp(MyApp());
    }

    ObjectBox Queries

    Discover how to use the Query API to create queries with ObjectBox DB. By utilizing these queries, you can retrieve stored objects that meet user-defined criteria.

    Build a query

    Use box.query(condition) and supply a condition on one or more properties to start building a query.

    Create a condition by accessing a property via the underscore class of the entity, e.g. User_.firstName.equal("Joe").

    Use build() to create a re-usable query instance. To then retrieve all results use find() on the query. More options on retrieving results are discussed later in .

    Once done, close() the query to clean up resources.

    Here is a full example to query for all users with the first name “Joe”:

    To combine multiple conditions use and(condition) and or(condition). This implicitly adds parentheses around the combined conditions, e.g. cond1.and(cond2) is logically equivalent to (cond1 AND cond2).

    For example to get users with the first name “Joe” that are born later than 2015 and whose last name starts with “O”:

    This also makes it possible to make a condition optional:

    To nest conditions pass a combined condition to and() or or():

    one_of is not yet available in Python.

    Other notable features

    • In Kotlin, instead of condition1.and(condition2) you can write condition1`` and ``condition2 (similarly condition1`` or ``condition2).

    • In Dart and Python, instead of condition1.and(condition2)

    Common conditions

    Apart from the standard conditions like equal(), notEqual(), greater() and less() there are also additional conditions available:

    • isNull() and notNull(),

    • between() to filter for values that are between the given two,

    • oneOf() and notOneOf()

    See the API for a full list:

    • Property conditions: and

    • Relation conditions:

    Dart only: DateTime caveat

    For Dart DateTime is stored as time in milliseconds internally in the database (or nanoseconds for @Property(type: PropertyType.dateNano)).

    To avoid having to manually convert to int when creating a query condition, extra methods that accept DateTime exist.

    Nearest neighbor vector search

    A special condition is available for vector properties with an HNSW index. See the dedicated page for details:

    Order results

    In addition to specifying conditions, you can order the returned results using the order() method. By default this sorts ASCII characters in alphabetical order while ignoring case and numbers in ascending order.

    Order results feature is not yet available in Python.

    You can also pass flags to order() to sort in descending order, to sort case sensitive or to specially treat null values. For example to sort the above results in descending order and case sensitive instead:

    Order results feature is not yet available in Python.

    Order directives can also be chained. Check the method documentation () for details.

    Run a query

    are first created (and not yet executed) by calling build() on the QueryBuilder.

    Once the query is created, it allows various operations, which we will explore in the following sub sections.

    Find objects

    There are a couple of find methods to retrieve objects matching the query:

    To return all entities matching the query simply call find().

    To only return the first result, use findFirst().

    If you expect a unique result, call findUnique() instead. It will give you a single result or null, if no matching entity was found and throw an exception if there was more than one result.

    Remove objects

    To remove all objects matching a query, call query.remove() .

    Reuse Queries and Parameters

    If you frequently run the same query you should cache the Query object and re-use it. To make a Query more reusable you can change the values, or query parameters, of each condition you added even after the Query is built. Let's see how.

    Query is not thread safe. To use a query in a different thread, either build a new query or synchronize access to it. Alternatively, in Java use query.copy() or a QueryThreadLocal to obtain an instance for each thread.

    Assume we want to find a list of User with specific firstName values. First, we build a regular Query with an equal() condition for firstName. Because we have to pass an initial parameter value to equal() but plan to override it before running the Query later, we just pass an empty string:

    Now at some later point, we want to run the Query with an actual value for the equals condition onfirstName :

    You might already be wondering what happens if you have more than one condition using firstName? For this purpose you can assign each parameter an alias while specifying the condition:

    Then, when setting a new parameter value pass the alias instead of the property:

    Limit, Offset, and Pagination

    Sometimes you only need a subset of a query, for example, the first 10 elements to display in your user interface. This is especially helpful (and resource-efficient) when you have a high number of entities and you cannot limit the result using query conditions only.

    offset: The first offset results are skipped.

    limit: At most limit results are returned.

    Lazy-load results (Java)

    Only Java/Kotlin

    To avoid loading query results right away, Query offers findLazy() and findLazyCached() which return a of the query results.

    is a thread-safe, unmodifiable list that reads entities lazily only once they are accessed. Depending on the find method called, the lazy list will be cached or not. Cached lazy lists store the previously accessed objects to avoid loading entities more than once. Some features of the list are limited to cached lists (e.g. features that require the entire list). See the for more details.

    Stream results (Dart)

    Only Dart

    Instead of reading the whole result (list of objects) using find() you can stream it using stream() :

    Observe or listen to changes

    To learn how to observe or listen to changes to the results of a query, see the data observers page:

    Query a single property

    If you only want to return the values of a particular property and not a list of full objects you can use a . After building a query, simply call property(Property) to define the property followed by the appropriate find method.

    For example, instead of getting all Users, to just get their email addresses:

    Note: the returned array of property values is not in any particular order, even if you did specify an order when building the query.

    Handle null values

    By default, null values are not returned. However, you can specify a replacement value to return if a property is null:

    Distinct and unique results

    The property query can also only return distinct values:

    Aggregate values

    Property queries ( and ) also offer aggregate functions to directly calculate the minimum, maximum, average, sum and count of all found values:

    • min() / minDouble(): Finds the minimum value for the given property over all objects matching the query.

    • max() / maxDouble(): Finds the maximum value.

    • sum()

    Query a related entity (links)

    After creating a relation between entities, you might want to add a query condition for a property that only exists in the related entity. In SQL this is solved using JOINs. But as ObjectBox is not a SQL database we built something very similar: links. Links are based on - see the doc page for the introduction.

    Assume there is a Person that can be associated with multiple Address entities:

    To get a Person with a certain name that also lives on a specific street, we need to query the associated Address entities of a Person. To do this, use the link() method of the query builder to tell that the addresses relation should be queried. Then add a condition for Address:

    What if we want to get a list of Address instead of Person? If you know ObjectBox relations well, you would probably add a @Backlink relation to Address and build your query using it with link() as shown above:

    But actually, you do not have to modify the Address entity (you still can if you need the @Backlink elsewhere). Instead, we can use the backlink() method to create a backlink to the addresses relation from Person just for that query:

    Eager-load relations

    Only Java/Kotlin

    By default are loaded lazily: when you first access a ToOne or ToMany property it will perform a database lookup to get its data. On each subsequent access it will use a cached version of that data.

    While this initial lookup is fast, you might want to prefetch ToOne or ToMany values before the query results are returned. To do this call the method when building your query and pass the RelationInfo objects associated with the ToOne and ToMany properties to prefetch:

    Eager loading only works one level deep. If you have nested relations and you want to prefetch relations of all children, you can instead add a query filter as described below. Use it to simply access all relation properties, which triggers them to lookup there values as described above.

    Query filters

    Only Java/Kotlin. For Dart, use the built-in method.

    Query filters come into play when you are looking for objects that need to match complex conditions, which cannot be fully expressed with the QueryBuilder class. Filters are written in Java and thus can express any complexity. Needless to say, that database conditions can be matched more efficiently than Java-based filters. Thus you will get the best results when you use both together:

    1. Narrow down results using standard database conditions to a reasonable number (use QueryBuilder to get “candidates”)

    2. Now filter those candidates using the Java interface to identify final results

    A QueryFilter implementation looks at one candidate object at a time and returns true if the candidate is a result or false if not.

    Example:

    Notes on performance: 1) ObjectBox creates objects very fast. 2) The virtual machine is tuned to garbage collect short-lived objects. Notes 1) and 2) combined makes a case for filtering because ObjectBox creates candidate objects of which some are not used and thus get garbage collected quickly after their creation.

    Query filters and ToMany relation

    The ToMany class offers additional methods that can be convenient in query filters:

    • hasA: returns true if one of the elements matches the given QueryFilter

    • hasAll: returns true if all of the elements match the given QueryFilter

    • getById: return the element with the given ID (value of the property with the @Id annotation)

    Debug queries

    To see what query is actually executed by ObjectBox:

    Then in your console (or logcat on Android) you will see log output like:

    Video Tutorial on Getting Started with ObjectBox for Flutter
    you can write
    condition1``
    &
    ``condition2
    (similarly
    condition1``
    |
    ``conditon2
    ).
  • Use condition.alias(aliasName) to set an alias for a condition that can later be used to change the parameter value of the condition on the built query.

  • to filter for values that match any in the given array,
  • startsWith(), endsWith() and contains() for extended String filtering.

  • For example, to query an
    Order
    entity with a
    date
    field to find all orders in 2023:
    /
    sumDouble()
    : Calculates the sum of all values.
    Note: the non-double version detects overflows and throws an exception in that case.
  • avg() : Calculates the average (always a double) of all values.

  • count(): returns the number of results. This is faster than finding and getting the length of the result array. Can be combined with distinct() to count only the number of distinct values.

  • Java
    Dart
    Java
    On-Device Vector Search
    Java
    Queries
    LazyList
    LazyList
    LazyList class documentation
    Data Observers & Rx
    PropertyQuery
    JavaDoc
    Dart API docs
    Relations
    relations
    QueryBuilder.eager
    where()
    QueryFilter
    Run a query
    Query<User> query = userBox.query(User_.firstName.equal("Joe")).build();
    List<User> joes = query.find();
    query.close();
    val query = userBox.query(User_.firstName.equal("Joe")).build()
    val joes = query.find()
    query.close()
    Query<User> query = userBox.query(User_.firstName.equals('Joe')).build();
    List<User> joes = query.find();
    query.close();
    Query<User> query = userBox.query(
            User_.firstName.equal("Joe")
                    .and(User_.yearOfBirth.greater(2015))
                    .and(User_.lastName.startsWith("O"))
    ).build();
    List<User> youngJoes = query.find();
    query.close();
    val query = userBox.query(
        User_.firstName equal "Joe" and
                (User_.yearOfBirth greater 2015) and
                (User_.lastName startsWith "O")
    ).build()
    val youngJoes = query.find()
    query.close()
    Query<User> query = userBox
        .query(User_.firstName.equals('Joe')
            .and(User_.yearOfBirth.greaterThan(2015))
            .and(User_.lastName.startsWith('O')))
        .build();
    
    // or use operator overloads:
    Query<User> query2 = userBox
        .query(User_.firstName.equals('Joe') &
            User_.yearOfBirth.greaterThan(2015) &
            User_.lastName.startsWith('O'))
        .build();
    query = userBox.query(User.firstName.equals("Joe")).build()
    joes = query.find()
    query = userBox.query( 
      User.firstName.equals("Joe") & 
      User.yearOfBirth.greater_than(2015) & 
      User.lastName.starts_with('O')
    ).build()
    joes = query.find()
    QueryCondition<User> conditions = User_.firstName.equal("Joe");
    if (onlyYoungJoes) {
        conditions = conditions.and(User_.yearOfBirth.greater(2015));
    }
    Query<User> query = userBox.query(conditions).build();
    var conditions: QueryCondition<User> = User_.firstName equal "Joe"
    if (onlyYoungJoes) {
        conditions = conditions and (User_.yearOfBirth greater 2015)
    }
    val query = userBox.query(conditions).build()
    var conditions = User_.firstName.equals('Joe');
    if (onlyYoungJoes) {
      conditions = conditions & User_.yearOfBirth.greaterThan(2015);
    }
    Query<User> query = userBox.query(conditions).build();
    // equal AND (less OR oneOf)
    Query<User> query = box.query(
            User_.firstName.equal("Joe")
                    .and(User_.age.less(12)
                            .or(User_.stamp.oneOf(new long[]{1012}))))
            .build();
    // equal AND (less OR oneOf)
    val query = box.query(
            User_.firstName equal "Joe" and
                    (User_.age less 12 or 
                            (User_.stamp oneOf longArrayOf(1012))))
            .build()        
    Query<User> query = box.query(
        User_.firstName.equals('Joe')
            .and(User_.age.lessThan(12)
            .or(User_.stamp.oneOf([1012]))))
        .build();
    query = userBox.query(
            (User.firstName.equals("Joe")
                & User.yearOfBirth.greater_than(1970)) | 
            User.lastName.starts_with('O')
        ).build()
    joes = query.find()
    Query<User> query = userBox
        .query(User_.firstName.equal("Joe"))
        .order(User_.lastName) // in ascending order, ignoring case
        .build();
    val query = userBox
        .query(User_.firstName.equal("Joe"))
        .order(User_.lastName) // in ascending order, ignoring case
        .build()
    // in ascending order, ignoring case
    final qBuilder = box.query(User_.firstName.equals('Joe')).order(User_.lastName);
    final query = qBuilder.build();
    .order(User_.lastName, QueryBuilder.DESCENDING | QueryBuilder.CASE_SENSITIVE)
    .order(User_.lastName, QueryBuilder.DESCENDING or QueryBuilder.CASE_SENSITIVE)
    .order(User_.lastName, flags: Order.descending | Order.caseSensitive)
    Query<User> query = builder.build();
    // return all entities matching the query
    List<User> joes = query.find();
    
    // return only the first result or null if none
    User joe = query.findFirst();
    
    // return the only result or null if none, throw if more than one result
    User joe = query.findUnique();
    // build a query
    Query<User> query = userBox.query(User_.firstName.equal("")).build();
    // build a query
    val query = userBox.query(User_.firstName.equal("")).build()
    // build a query
    final query = userBox.query(User_.firstName.equals('')).build();
    # build a query
    query = userBox.query(User.firstName.equals('')).build();
    // Change firstName parameter to "Joe" and get results
    List<User> joes = query.setParameter(User_.firstName, "Joe").find();
    
    // Change firstName parameter to "Jake" and get results
    List<User> jakes = query.setParameter(User_.firstName, "Jake").find();
    // Change firstName parameter to "Joe" and get results
    val joes = query.setParameter(User_.firstName, "Joe").find()
    
    // Change firstName parameter to "Jake" and get results
    val jakes = query.setParameter(User_.firstName, "Jake").find()
    // Change firstName parameter to "Joe" and get results
    query.param(User_.firstName).value = 'Joe';
    final joes = query.find();
    
    // Change firstName parameter to "Jake" and get results
    final jakes = (query..param(User_.firstName).value = 'Jake').find();
    # Change firstName parameter to "Joe" and get results
    joes = query.set_parameter_string(User.firstName, "Joe").find()
    
    # Change firstName parameter to "Jake" and get results
    jakes = query.set_parameter_srting(User.firstName, "Jake").find()
    // assign alias "name" to the equal query parameter
    Query<User> query = userBox
        .query(User_.firstName.equal("").alias("name"));
    // assign alias "name" to the equal query parameter
    val query = userBox
        .query(User_.firstName.equal("").alias("name"))
    // assign alias "name" to the equals query parameter
    final query = userBox.query(User_.firstName.equals('', alias: 'name')).build();
    # Assign alias "name" to the equals query parameter
    query = userBox.query(User.firstName.equals('').alias("name")).build();
    // Change parameter with alias "name" to "Joe", get results
    List<User> joes = query.setParameter("name", "Joe").find();
    // Change parameter with alias "name" to "Joe" and get results
    val joes = query.setParameter("name", "Joe").find()
    // Change parameter with alias "name" to "Joe" and get results
    final joes = (query..param(User_.firstName, alias: 'name').value = 'Joe').find();
    # Change parameter with alias "name" to "Joe" and get results
    joes = query.set_parameter_alias_string("name", "Joe").find()
    // offset by 10, limit to at most 5 results
    List<User> joes = query.find(10, 5);
    // offset by 10, limit to at most 5 results
    val joes = query.find(10, 5)
    // offset by 10, limit to at most 5 results
    query
      ..offset = 10
      ..limit = 5;
    List<User> joes = query.find();
    # Offset by 10, limit to at most 5 results
    joes = query \
        .offset(10)
        .limit(5)
        .find()
    Query<User> query = userBox.query().build();
    Stream<User stream = query.stream();
    await stream.forEach((User user) => print(user));
    query.close();
    String[] emails = userBox.query().build()
        .property(User_.email)
        .findStrings();
        
    // or use .findString() to return just the first result
    val emails = userBox.query().build()
        .property(User_.email)
        .findStrings()
        
    // or use .findString() to return just the first result
    final query = userBox.query().build();
    List<String> emails = query.property(User_.email).find();
    query.close();
    // includes 'unknown' for each null email
    String[] emails = userBox.query().build()
        .property(User_.email)
        .nullValue("unknown")
        .findStrings();
    // includes 'unknown' for each null email
    val emails = userBox.query().build()
        .property(User_.email)
        .nullValue("unknown")
        .findStrings()
    final query = userBox.query().build();
    // includes 'unknown' for each null email
    List<String> emails = query.property(User_.email).find(replaceNullWith: 'unknown');
    query.close();
    PropertyQuery pq = userBox.query().build().property(User_.firstName);
    
    // returns ['joe'] because by default, the case of strings is ignored.
    String[] names = pq.distinct().findStrings();
    
    // returns ['Joe', 'joe', 'JOE']
    String[] names = pq.distinct(StringOrder.CASE_SENSITIVE).findStrings();
    
    // the query can be configured to throw there is more than one value
    String[] names = pq.unique().findStrings();
    val pq = userBox.query().build().property(User_.firstName)
    
    // returns ['joe'] because by default, the case of strings is ignored.
    val names = pq.distinct().findStrings()
    
    // returns ['Joe', 'joe', 'JOE']
    val names = pq.distinct(StringOrder.CASE_SENSITIVE).findStrings()
    
    // the query can be configured to throw there is more than one value
    val names = pq.unique().findStrings()
    final query = userBox.query().build();
    PropertyQuery<String> pq = query.property(User_.firstName);
    pq.distinct = true;
    
    // returns ['Joe', 'joe', 'JOE'] 
    List<String> names = pq.find();
    
    // returns ['joe']
    pq.caseSensitive = false;
    List<String> names = pq.find(); 
    query.close();
    @Entity
    public class Person {
        @Id long id;
        String name;
        ToMany<Address> addresses;
    }
    
    @Entity
    public class Address {
        @Id long id;
        String street;
        String zip;
    }
    @Entity
    class Person {
        @Id
        var id: Long = 0
        var name: String? = null
        lateinit var addresses: ToMany<Address>
    }
    
    @Entity
    class Address {
        @Id
        var id: Long = 0
        var street: String? = null
        var zip: String? = null
    }
    @Entity()
    class Person {
        int id;
        String name;
        final addresses = ToMany<Address>();
    }
    
    @Entity()
    class Address {
        int id;
        String street;
        String zip;
    }
    // get all Person objects named "Elmo"...
    QueryBuilder<Person> builder = personBox
        .query(Person_.name.equal("Elmo"));
    // ...which have an address on "Sesame Street"
    builder.link(Person_.addresses)
        .apply(Address_.street.equal("Sesame Street"));
    List<Person> elmosOnSesameStreet = builder.build().find();
    // get all Person objects named "Elmo"...
    val builder = personBox
        .query(Person_.name.equal("Elmo"))
    // ...which have an address on "Sesame Street"
    builder.link(Person_.addresses)
        .apply(Address_.street.equal("Sesame Street"))
    val elmosOnSesameStreet = builder.build().find()
    // get all Person objects named "Elmo"...
    QueryBuilder<Person> builder = personBox
        .query(Person_.name.equals('Elmo'));
    // ...which have an address on "Sesame Street"
    builder.linkMany(Person_.addresses, Address_.street.equals('Sesame Street'));
    Query<Person> query = builder.build();
    List<Person> elmosOnSesameStreet = query.find();
    query.close();
    @Entity
    public class Address {
        // ...
        @Backlink(to = "addresses")
        ToMany<Person> persons;
    }
    
    // get all Address objects with street "Sesame Street"...
    QueryBuilder<Address> builder = addressBox
        .query(Address_.street.equal("Sesame Street"));
    // ...which are linked from a Person named "Elmo"
    builder.link(Address_.persons)
        .apply(Person_.name.equal("Elmo"));
    List<Address> sesameStreetsWithElmo = builder.build().find();
    @Entity
    class Address {
        // ...
        @Backlink(to = "addresses")
        lateinit var persons: ToMany<Person>
    }
    
    // get all Address objects with street "Sesame Street"...
    val builder = addressBox
        .query(Address_.street.equal("Sesame Street"))
    // ...which are linked from a Person named "Elmo"
    builder.link(Address_.persons)
        .apply(Person_.name.equal("Elmo")
    val sesameStreetsWithElmo = builder.build().find()
    @Entity()
    class Address {
        ...
        
        @Backlink()
        final persons = ToMany<Person>();
    }
    
    // get all Address objects with street "Sesame Street"...
    QueryBuilder<Address> builder = 
        addressBox.query(Address_.street.equals('Sesame Street'));
    // ...which are linked from a Person named "Elmo"
    builder.linkMany(Address_.persons, Person_.name.equals('Elmo'));
    Query<Address> query = builder.build();
    List<Address> sesameStreetsWithElmo = query.find();
    query.close();
    // get all Address objects with street "Sesame Street"...
    QueryBuilder<Address> builder = addressBox
        .query(Address_.street.equal("Sesame Street"));
    // ...which are linked from a Person named "Elmo"
    builder.backlink(Person_.addresses)
        .apply(Person_.name.equal("Elmo"));
    List<Address> sesameStreetsWithElmo = builder.build().find();
    // get all Address objects with street "Sesame Street"...
    val builder = addressBox
        .query(Address_.street.equal("Sesame Street"))
    // ...which are linked from a Person named "Elmo"
    builder.backlink(Person_.addresses)
        .apply(Person_.name.equal("Elmo"))
    val sesameStreetsWithElmo = builder.build().find()
    // get all Address objects with street "Sesame Street"...
    QueryBuilder<Address> builder = 
        addressBox.query(Address_.street.equals('Sesame Street'));
    // ...which are linked from a Person named "Elmo"
    builder.backlinkMany(Person_.addresses, Person_.name.equals('Elmo'));
    Query<Address> query = builder.build();
    List<Address> sesameStreetsWithElmo = query.find();
    query.close();
    List<Customer> customers = customerBox.query().build().find();
    // Customer has a ToMany called orders.
    // First access: this will cause a database lookup.
    Order order = customers.get(0).orders.get(0);
    val customers = customerBox.query().build().find()
    // Customer has a ToMany called orders
    val order = customers[0].orders[0] // first access: causes a database lookup
    List<Customer> customers = customerBox.query()
        .eager(Customer_.orders) // Customer has a ToMany called orders.
        .build()
        .find();
    // First access: this will cause a database lookup.
    Order order = customers.get(0).orders.get(0);
    val customers = customerBox.query()
        .eager(Customer_.orders) // Customer has a ToMany called orders
        .build()
        .find()
    customers[0].orders[0] // first access: this will NOT cause a database lookup
    // Reduce object count to reasonable value.
    songBox.query(Song_.bandId.equal(bandId))
            // Filter is performed on candidate objects.
            .filter((song) -> song.starCount * 2 > song.downloads);
    // Set the LOG_QUERY_PARAMETERS debug flag
    BoxStore store = MyObjectBox.builder()
        .debugFlags(DebugFlags.LOG_QUERY_PARAMETERS)
        .build();
        
    // Execute a query
    query.find();
    // Set the LOG_QUERY_PARAMETERS debug flag
    val store = MyObjectBox.builder()
        .debugFlags(DebugFlags.LOG_QUERY_PARAMETERS)
        .build()
        
    // Execute a query
    query.find()
    print(query.describeParameters());
    Parameters for query #2:
    (firstName ==(i) "Joe"
     AND age < 12)
    final query = box
        .query(Order_.date.betweenDate(DateTime.utc(2023),
            DateTime.utc(2024).subtract(Duration(milliseconds: 1))))
        .build();
    Video Tutorial on Getting Started with ObjectBox for Android and Java

    Relations

    ObjectBox Relations: Learn how to create and update to-one and to-many relations between entities in ObjectBox and improve performance.

    ObjectBox - Relations

    Prefer to dive right into code? Check out our

    • using relations,

    • Android app,

    • app.

    The Python API does not yet support relations.

    Objects may reference other objects, for example using a simple reference or a list of objects. In database terms, we call those references relations. The object defining a relation we call the source object, the referenced object we call the target object. So a relation has a direction.

    If there is one target object, we call the relation to-one. And if there can be multiple target objects, we call it to-many. Relations are lazily initialized: the actual target objects are fetched from the database when they are first accessed. Once the target objects are fetched, they are cached for further accesses.

    To-One Relations

    You define a to-one relation using the ToOne class, a smart proxy to the target object. It gets and caches the target object transparently. For example, an order is typically made by one customer. Thus, we could model the Order class to have a to-one relation to the Customer like this:

    For Kotlin desktop (Linux, macOS, Windows) apps, additional code is required. See .

    Now let’s add a customer and some orders. To set the related customer object, call setTarget() (or assign target in Kotlin) on the ToOne instance and put the order object:

    If the customer object does not yet exist in the database (i.e. its ID is zero), ToOne will put it (so there will be two puts, one for Order, one for Customer). If it already exists, ToOne will only create the relation (so there's only one put for Order, as explicitly written in the code). See further below for details about .

    Note: if your related entity uses manually assigned IDs with @Id(assignable = true) ObjectBox won't know if a target object is a new one or an existing one, therefore it will NOT insert it, customerBox.put(customer) would have to be called manually (considering the previous example). See below about for details.

    Have a look at the following code how you can get (read) the customer of an order:

    This will do a database call on the first access (lazy loading). It uses lookup by ID, which is very fast in ObjectBox. If you only need the ID instead of the whole target object, you can completely avoid this database operation because it's already loaded: use order.customer.targetId/getTargetId().

    We can also remove the relationship to a customer:

    Note that this does not remove the customer from the database, it just dissolves the relationship.

    How ToOne works behind the scenes

    If you look at your model in objectbox-models/default.json (or lib/bjectbox-model.json in Dart) you can see, a ToOne property is not actually stored. Instead, the ID of the target object is saved in a virtual property named by default like the ToOne property appended with Id.

    Rename the ToOne target ID property

    To change the default name of the target ID property created for a ToOne relation, use the TargetIdProperty annotation:

    Expose the ToOne target ID property

    Only Java/Kotlin

    You can directly access the target ID property by defining a long (or Long in Kotlin) property in your entity class with the expected name:

    Initialization Magic

    Only Java/Kotlin

    Did you notice that the ToOne field customer was never initialized in the code example above? Why can the code still use customer without any NullPointerException? Because the field actually is initialized – the initialization code just is not visible in your sources.

    The ObjectBox Gradle plugin will transform your entity class (supported for Android projects and Java JVM projects) to do the proper initialization in constructors before your code is executed. Thus, even in your constructor code, you can just assume ToOne and ToMany/ List properties have been initialized and are ready for you to use.

    If your setup does not support transformations, currently Kotlin JVM (Linux, macOS, Windows) projects, add the below modifications yourself. You also will have to call box.attach(entity) before modifying ToOne or ToMany properties.

    Improve Performance

    Only Java/Kotlin

    To improve performance when ObjectBox constructs your entities, you should provide an all-properties constructor.

    For a ToOne you have to add an id parameter, typically named like the ToOne field appended with Id . Check your objectbox-models/default.json file to find the correct name.

    An example:

    To-Many Relations

    To define a to-many relation, you can use a property of type ToMany. As the ToOne class, the ToMany class helps you to keep track of changes and to apply them to the database.

    Note that to-many relations are resolved lazily on first access, and then cached in the source entity inside the ToMany object. So subsequent calls to any method, like size() of the ToMany, do not query the database, even if the relation was changed elsewhere. To get the latest data fetch the source entity again or call reset() on the ToMany.

    There is a slight difference if you require a one-to-many (1:N) or many-to-many (N:M) relation. A 1:N relation is like the example above where a customer can have multiple orders, but an order is only associated with a single customer. An example for an N:M relation is students and teachers: students can have classes by several teachers but a teacher can also instruct several students.

    One-to-Many (1:N)

    To define a one-to-many relation, you need to annotate your relation property with @Backlink. It links back to a to-one relation in the target object. Using the customer and orders example, we can modify the customer class to have a to-many relation to the customer's orders:

    For Kotlin desktop (Linux, macOS, Windows) apps, additional code is required. See .

    When using @Backlink it is recommended to explicitly specify the linked to relation using to. It is possible to omit this if there is only one matching relation. However, it helps with code readability and avoids a compile-time error if at any point another matching relation is added (in the above case, if another ToOne<Customer> is added to the Order class).

    Let’s add some orders together with a new customer. ToMany implements the Java List interface, so we can simply add orders to it:

    Two data classes that have the same property values (excluding those defined in the class body) . Keep this in mind when working with ToMany which uses a HashMap to keep track of changes. E.g. adding the same data class multiple times has no effect, it is treated as the same entity.

    If the order entities do not yet exist in the database, ToMany will put them. If they already exist, it will only create the relation (but not put them). See further below for details about .

    Note: if your entities use manually assigned IDs with @Id(assignable = true) the above will not work. See below about for details.

    We can easily get the orders of a customer back by accessing the list of orders:

    Removing orders from the relation works as expected:

    Many-to-Many (N:M)

    To define a many-to-many relation you simply add a property using the ToMany class. Assuming a students and teachers example, this is how a simple student class that has a to-many relation to teachers can look like:

    For Kotlin desktop (Linux, macOS, Windows) apps, additional code is required. See .

    Adding the teachers of a student works exactly like with a list:

    If the teacher entities do not yet exist in the database, ToMany will also put them. If they already exist, ToMany will only create the relation (but not put them). See further below for details about .

    Note: if your entities use manually assigned IDs with @Id(assignable = true) the above will not work. See below about for details.

    To get the teachers of a student we just access the list:

    And if a student drops out of a class, we can remove a teacher from the relation:

    Access Many-To-Many in the reverse direction

    Following the above example, you might want an easy way to find out what students a teacher has. Instead of having to perform a query, you can just add a to-many relation to the teacher and annotate it with the @Backlink annotation:

    For Kotlin desktop (Linux, macOS, Windows) apps, additional code is required. See .

    Using the List interface for to-many

    Only for Java/Kotlin

    Instead of the ToMany type it is also possible to use List (or MutableList in Kotlin) for a to-many property. At runtime the property will still be a ToMany instance (ToMany does implement the List interface) due to the initialization magic described above, or if manually initialized as seen in the example below.

    This may be helpful when trying to deserialize an object that contains a to-many from JSON. However, note that if the JSON deserializer replaces the ToMany instance with e.g. an ArrayList during put the to-many property is skipped. It is then up to you to create the relation.

    Updating Relations

    The ToOne and ToMany relation classes assist you to persist the relation state. They keep track of changes and apply them to the database once you put the Object containing them. ObjectBox supports relation updates for new (not yet persisted; ID == 0) and existing (persisted before; ID != 0) Objects.

    For convenience, ToOne and ToMany will put Objects added to them if they do not yet exist (ID == 0). If they already exist (their ID != 0, or you are using @Id(assignable = true)), only the relation will be created or destroyed (internally the Object ID is added to or removed from the relation). In that case, to put changes to the properties of related Objects use their specific Box instead:

    Updating ToOne

    The ToOne class offers the following methods to update the relation:

    • setTarget(target) makes the given (new or existing) Object the new relation target; pass null to clear the relation.

    • setTargetId(targetId) sets the relation target based on the given ID of an existing Object; pass 0 (zero) to clear the relation.

    Note: attach the Box before calling setAndPutTarget() on a new (not put) Object owning a ToOne:

    Note: if the target Object class uses manually assigned IDs with @Id(assignable = true) it will not be put when the Object that owns the relation is put:

    This is because ObjectBox only puts related entities with an ID of 0. See the for background information.

    Updating ToMany

    The ToMany relation class is based on a standard List with added change tracking for Objects. As , it will put new Objects (ID == 0) that are added to it once the Object owning it is put. And when removing Objects from it, just the relation is cleared, the Objects are not removed from their Box.

    See the documentation on and above for details.

    Note (Java/Kotlin only): if your entities are using manually assigned IDs with @Id(assignable = true) additional steps are required. Read on for details:

    If the owning, source Object uses @Id(assignable = true) attach its Box before modifying its ToMany:

    If the target Object, like Order above, is using manually assigned IDs put the target Objects before adding them to the ToMany relation:

    The above steps are required because, when putting the Object owning the ToMany only the relation is updated. This is because ObjectBox only puts target Objects with an ID of 0. See the for the background information.

    Example: Extending the Model with an Address

    A typical extension to the customer/order example we have started earlier would be to add an Address type. While you could model street, ZIP and so on directly into Customer, it usually makes sense to normalize that out into a separate entity. And when you think about the usual shopping sites, they all let you have multiple addresses...

    So, typically, this is how you would model that:

    • an address is bound to a customer; add a ToOne<Customer> customer to Address

    • an order is shipped to one address, and might have a diverging billing address optionally; add ToOne<Address> shippingAddress and ToOne<Address> billingAddress to Order

    Example: Modelling Tree Relations

    You can model a tree relation with a to-one and a to-many relation pointing to itself:

    For Kotlin desktop (Linux, macOS, Windows) apps, additional code is required. See .

    This lets you navigate a tree nodes parent and children:

    Java/Kotlin only:
    setAndPutTarget(target)
    makes the given (new or existing) Object the new relation target and puts the owning, source Object and if needed the target Object.
    For each ToOne you could have a matching ToMany on the other side of the relation (backlink)
    Kotlin Android example app
    Java relations playground
    Flutter relations example
    updating relations
    updating ToOne
    are equal and have the same hash code
    updating relations
    updating ToMany
    updating relations
    updating ToMany
    documentation about IDs
    mentioned above
    One-to-Many
    Many-to-Many
    documentation about IDs
    To-one Relations
    One-to-Many (1:N)
    Many-to-Many (N:M)
    Initialization Magic
    Initialization Magic
    Initialization Magic
    Initialization Magic
    Initialization Magic
    // Customer.java
    @Entity
    public class Customer {
        
        @Id public long id;
        
    }
    
    // Order.java
    @Entity
    public class Order {
        
        @Id public long id;
        
        public ToOne<Customer> customer;
        
    }
    @Entity
    data class Customer(
            @Id var id: Long = 0
    )
    
    @Entity
    data class Order(
            @Id var id: Long = 0
    ) {
        lateinit var customer: ToOne<Customer>
    }
    @Entity()
    class Customer {
      int id;
    }
    
    @Entity()
    class Order {
      int id;
      
      final customer = ToOne<Customer>();
    }
    Customer customer = new Customer();
    Order order = new Order();
    order.customer.setTarget(customer);
    // Puts order and customer:
    long orderId = boxStore.boxFor(Order.class).put(order);
    val customer = Customer()
    val order = Order()
    order.customer.target = customer
    // Puts order and customer:
    val orderId = boxStore.boxFor(Order::class.java).put(order)
    final customer = Customer();
    final order = Order();
    
    // set the relation
    order.customer.target = customer;
    // Or you could create the target object in place:
    // order.customer.target = Customer();
    
    // Save the order and customer to the database
    int orderId = store.box<Order>().put(order);
    Order order = boxStore.boxFor(Order.class).get(orderId);
    Customer customer = order.customer.getTarget();
    val order = boxStore.boxFor(Order::class.java)[orderId]
    val customer = order.customer.target
    final order = store.box<Order>().get(orderId);
    final customer = order.customer.target;
    order.customer.setTarget(null);
    boxStore.boxFor(Order.class).put(order);
    order.customer.target = null
    boxStore.boxFor(Order::class.java).put(order)
    order.customer.target = null; // same as  .targetId = 0;
    store.box<Order>().put(order);
    @Entity
    public class Order {
        // Change from default "customerId" to "customerRef"
        @TargetIdProperty("customerRef")
        public ToOne<Customer> customer;
        // Optional: expose target ID property (using changed name)
        public long customerRef;
    }
    @Entity()
    class Order {
        // Change from default "customerId" to "customerRef"
        @TargetIdProperty("customerRef")
        final customer = ToOne<Customer>();
    }
    @Entity
    public class Order {
        @Id public long id;
        
        public long customerId; // ToOne target ID property
        public ToOne<Customer> customer;
    }
    @Entity
    data class Order(
            @Id var id: Long = 0,
            var customerId: Long = 0
    ) {
        lateinit var customer: ToOne<Customer>
    }
    @Entity
    public class Example {
    ​    
        // Initialize ToOne and ToMany manually.
        ToOne<Order> order = new ToOne<>(this, Example_.order);   
        ToMany<Order> orders = new ToMany<>(this, Example_.orders);
     
        // Add a BoxStore field.
        transient BoxStore __boxStore;
        
    }
    @Entity
    class Example() {
    
        // Initialize ToOne and ToMany manually.
        var order = ToOne<Order>(this, Example_.order)   
        var orders = ToMany<Order>(this, Example_.orders)
    
        // Add a BoxStore field.
        @JvmField
        @Transient
        @Suppress("PropertyName")
        var __boxStore: BoxStore? = null
    
    }
    @Entity
    public class Order {
        
        @Id public long id;
        
        public ToOne<Customer> customer;
        
        public Order() { /* default constructor */ }
        
        public Order(long id, long customerId /* virtual ToOne id property */) {
            this.id = id;
            this.customer.setTargetId(customerId);
        }
        
    }
    // Customer.java
    @Entity
    public class Customer {
        
        @Id public long id;
        
        @Backlink(to = "customer")
        public ToMany<Order> orders;
        
    }
    
    // Order.java
    @Entity
    public class Order {
        
        @Id public long id;
        public String name;
        
        public ToOne<Customer> customer;
        
        public Order(String name) {
            this.name = name;
        }
        
        public Order() {
        }
    }
    @Entity
    data class Customer(
            @Id var id: Long = 0
    ) {    
        @Backlink(to = "customer")
        lateinit var orders: ToMany<Order>
    }
    
    @Entity
    data class Order(
            @Id var id: Long = 0,
            var name: String? = ""
    ) {
        lateinit var customer: ToOne<Customer>
    }
    @Entity()
    class Customer {
      int id;
    
      @Backlink('customer')
      final orders = ToMany<Order>();
    }
    
    @Entity()
    class Order {
      int id;
    
      final customer = ToOne<Customer>();
    }
    Customer customer = new Customer();
    customer.orders.add(new Order("Order #1"));
    customer.orders.add(new Order("Order #2"));
    // Puts customer and orders:
    long customerId = boxStore.boxFor(Customer.class).put(customer);
    val customer = Customer()
    customer.orders.add(Order("Order #1"))
    customer.orders.add(Order("Order #2"))
    // Puts customer and orders:
    val customerId = boxStore.boxFor(Customer::class.java).put(customer)
    Customer customer = Customer();
    customer.orders.add(Order()); // Order #1
    customer.orders.add(Order()); // Order #2
    // Puts customer and orders:
    final customerId = store.box<Customer>().put(customer);
    Customer customer = boxStore.boxFor(Customer.class).get(customerId);
    for (Order order : customer.orders) {
        // Do something with each order.
    }
    val customer = boxStore.boxFor(Customer::class.java).get(customerId)
    for (order in customer.orders) {
        // Do something with each order.
    }
    Customer customer = store.box<Customer>().get(customerId);
    // you can use any List<> functions, for example:
    final valueSum = customer.orders.fold(0, (sum, order) => sum + order.value);
    // though you could use property queries and their .sum() function for that
    // Remove the relation to the first order in the list
    Order order = customer.orders.remove(0);
    boxStore.boxFor(Customer.class).put(customer);
    // Optional: also remove the order entity from its box:
    // boxStore.boxFor(Order.class).remove(order);
    // Remove the relation to the first order in the list
    val order = customer.orders.removeAt(0)
    boxStore.boxFor(Customer::class.java).put(customer)
    // Optional: also remove the order entity from its box:
    // boxStore.boxFor(Order::class.java).remove(order)
    // Remove the relation to the first order in the list
    Order order = customer.orders.removeAt(0);
    store.box<Customer>().put(customer);
    // Optional: also remove the order entity from its box:
    // store.box<Order>().remove(order);
    // Teacher.java
    @Entity
    public class Teacher{
        
        @Id public long id;
        public String name;
        
        public Teacher(String name) {
            this.name = name;
        }
        
        public Teacher() {
        }
    }
    
    // Student.java
    @Entity
    public class Student{
        
        @Id public long id;
        
        public ToMany<Teacher> teachers;
        
    }
    @Entity
    data class Teacher(
            @Id var id: Long = 0,
            var name: String? = ""
    )
    
    @Entity
    data class Student(
            @Id var id: Long = 0
    ) {
        lateinit var teachers: ToMany<Teacher>
    }
    @Entity()
    class Teacher{
      int id;
    }
    
    @Entity()
    class Student{
      int id;
      
      final teachers = ToMany<Teacher>();
    }
    Teacher teacher1 = new Teacher("Teacher 1");
    Teacher teacher2 = new Teacher("Teacher 2");
    
    Student student1 = new Student();
    student1.teachers.add(teacher1);
    student1.teachers.add(teacher2);
    
    Student student2 = new Student();
    student2.teachers.add(teacher2);
    
    // Puts students and teachers:
    boxStore.boxFor(Student.class).put(student1, student2);
    val teacher1 = Teacher("Teacher 1")
    val teacher2 = Teacher("Teacher 2")
    
    val student1 = Student()
    student1.teachers.add(teacher1)
    student1.teachers.add(teacher2)
    
    val student2 = Student()
    student2.teachers.add(teacher2)
    
    // Puts students and teachers:
    boxStore.boxFor(Student::class.java).put(student1, student2)
    Teacher teacher1 = Teacher();
    Teacher teacher2 = Teacher();
    
    Student student1 = Student();
    student1.teachers.add(teacher1);
    student1.teachers.add(teacher2);
    
    Student student2 = Student();
    student2.teachers.add(teacher2);
    
    // saves students as well as teachers in the database
    store.box<Student>().putMany([student1, student2]);
    Student student = boxStore.boxFor(Student.class).get(studentId);
    for (Teacher teacher : student.teachers) {
        // Do something with each teacher.
    }
    val student = boxStore.boxFor(Student::class.java).get(studentId)
    for (teacher in student.teachers) {
        // Do something with each teacher.
    }
    Student student = store.box<Student>().get(studentId);
    // you can use any List<> functions, for example:
    student.teachers.forEach((Teacher teacher) => ...);
    student.teachers.remove(0);
    // Simply put the student again:
    // boxStore.boxFor(Student.class).put(student);
    // Or more efficient (than writing the thole object), store just the relations:
    student.teachers.applyChangesToDb();
    student.teachers.removeAt(0)
    // Simply put the student again:
    // boxStore.boxFor(Student::class.java).put(student)
    // Or more efficient (than writing the thole object), store just the relations:
    student.teachers.applyChangesToDb()
    student.teachers.removeAt(0)
    // Simply put the student again:
    // store.box<Student>().put(student);
    // Or more efficient (than writing the thole object), store just the relations:
    student.teachers.applyToDb();
    // Teacher.java
    @Entity
    public class Teacher{
        
        @Id public long id;
        
        // Backed by the to-many relation in Student:
        @Backlink(to = "teachers")
        public ToMany<Student> students;
        
    }
    
    // Student.java
    @Entity
    public class Student{
        
        @Id public long id;
        
        public ToMany<Teacher> teachers;
        
    }
    @Entity
    data class Teacher(
            @Id var id: Long = 0
    ) {
        // Backed by the to-many relation in Student:
        @Backlink(to = "teachers")
        lateinit var students: ToMany<Student>
    }
    
    @Entity
    data class Student(
            @Id var id: Long = 0
    ) {
        lateinit var teachers: ToMany<Teacher>
    }
    @Entity()
    class Teacher{
      int id;
      
      // Backed by the to-many relation in Student:
      @Backlink()
      final students = ToMany<Student>();
    }
    
    @Entity()
    public class Student{
      int id;
      
      final teachers = ToMany<Teacher>();
    }
    // Teacher.java
    @Entity
    public class Teacher{    
        @Id public long id;    
    }
    
    // Student.java
    @Entity
    public class Student{    
        @Id public long id;    
        public List<Teacher> teachers = new ToMany<>(this, Student_.teachers);   
    }
    @Entity
    data class Teacher(
            @Id var id: Long = 0
    )
    
    @Entity
    data class Student(
            @Id var id: Long = 0
    ) {
        var teachers: MutableList<Teacher> = ToMany<>(this, Student_.teachers)
    }
    Box<Student> studentBox = store.boxFor(Student.class);
    Student student = new Student();
    Teacher teacher = new Teacher();
    
    // Simulate what a JSON deserialzer would do:
    // replace ToMany instance with ArrayList.
    student.teachers = new ArrayList();
    student.teachers.add(teacher);
    // Put will skip the teachers property.
    studentBox.put(student);
    System.out.println(store.boxFor(Teacher.class).count());
    // prints 0
    
    // Need to manually create the relation.
    Student student2 = studentBox.get(student.id);
    student2.teachers.addAll(student.teachers);
    studentBox.put(student2);
    // update a related entity using its box
    Order orderToUpdate = customer.orders.get(0);
    orderToUpdate.text = "Revised description";
    // DOES NOT WORK
    // boxStore.boxFor(Customer.class).put(customer);
    // WORKS
    boxStore.boxFor(Order.class).put(orderToUpdate);
    // update a related entity using its box
    Order orderToUpdate = customer.orders.getAt(0);
    orderToUpdate.text = "Revised description";
    // DOES NOT WORK
    // boxStore.boxFor(Customer::class.java).put(customer);
    // WORKS
    boxStore.boxFor(Order::class.java).put(orderToUpdate);
    // update a related entity using its box
    Order orderToUpdate = customer.orders[0];
    orderToUpdate.text = 'Revised description';
    // DOES NOT WORK - the change to the order is not saved
    // store.box<Customer>().put(customer);
    // WORKS
    store.box<Order>().put(orderToUpdate);
    // Option 1: set target and put.
    order.customer.setTarget(customer);
    // Or set target via Object ID: 
    // order.customer.setCustomerId(customer.getId());
    orderBox.put(order);
    
    // Option 2: combined set target and put.
    order.customer.setAndPutTarget(customer);
    // Option 1: set target and put.
    order.customer.setTarget(customer)
    // Or set target via Object ID: 
    // order.customer.setCustomerId(customer.getId())
    orderBox.put(order)
    
    // Option 2: combined set target and put.
    order.customer.setAndPutTarget(customer)
    Order order = new Order();
    orderBox.attach(order);
    order.customer.setAndPutTarget(customer);
    Customer customer = new Customer();
    // If ID is manually assigned, put target Object first
    customer.id = 12;
    customerBox.put(customer);
    // Then can safely set as target
    order.customer.setTarget(customer);
    // Or set target via Object ID
    // order.customer.setCustomerId(customer.getId());
    orderBox.put(order);
    // If source has a manually assigned ID attach Box first
    customer.id = 12;
    customerBox.attach(customer);
    // Then can safely modify ToMany
    customer.orders.add(order);
    customerBox.put(customer);
    // If ID is manually assigned put target Object first
    order.id = 42;
    orderBox.put(order);
    // Then can safely add target Object to ToMany
    customer.orders.add(order);
    customerBox.put(customer);
    @Entity
    public class TreeNode {
        @Id long id;
        
        ToOne<TreeNode> parent;
        
        @Backlink
        ToMany<TreeNode> children;
    }
    @Entity
    data class TreeNode(
            @Id var id: Long = 0
    ) {
        lateinit var parent: ToOne<TreeNode>
        
        @Backlink
        lateinit var children: ToMany<TreeNode>
    }
    TreeNode parent = treeNode.parent.getTarget();
    List<TreeNode> children = treeNode.children;
    val parent: TreeNode = treeNode.parent.target
    val children: List<TreeNode> = treeNode.children
    to-one-relations
    Onte-to-Many
    Many-to-Many

    Java Release History (<= v1.5)

    Release notes for ObjectBox 1.5.0 and older.

    Although this is technically the changelog for Java, this is also a good reference of what changed in the ObjectBox C++ core.

    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 that demonstrates how to perform on-device .

    • 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 instead.

    4.0.0 - Vector Search - 2024-05-16

    ObjectBox now supports 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:

    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:

    For an introduction to Vector Search, more details and other supported languages see the .

    • 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.

    V3.8.0 - 2024-02-13

    • Support creating file-less in-memory databases, e.g. for caching and testing. To create one use inMemory() when building a BoxStore:

      See the BoxStoreBuilder.inMemory() documentation for details.

    • Change BoxStore.deleteAllFiles() to support deleting an in-memory database.

    V3.7.1 - 2023/11/07

    • Throw an exception instead of crashing when trying to create a query on a closed store.

    • The Gradle plugin now requires at least Gradle 7.0 and Android Gradle Plugin 4.1.

    • The Android library now requires Android 4.4 (API 19) or newer.

    V3.7.0 - 2023/08/22

    • A new key/value validation option validateOnOpenKv() is available on MyObjectBox.builder() to help diagnose FileCorruptException: Corrupt DB, min key size violated issues. If enabled, the build() call will throw a FileCorruptException if corruption is detected with details on which key/value is affected.

    • Admin: integer and floating point arrays introduced with the previous release are now nicely displayed and collapsed if long.

    V3.6.0 - 2023/05/16

    • Support for integer and floating point arrays: store

    • short[], char[], int[], long[] and

    • float[] and double[]

    V3.5.1 - 2023/01/31

    • Fixes writes failing with "error code -30786", which may occur in some corner cases on some devices.

    • Add docs to DbSchemaException on how to resolve its typical causes.

    V3.5.0 - 2022/12/05

    This release includes breaking changes to generated code. If you encounter build errors, make sure to clean and build your project (e.g. Build > Rebuild project in Android Studio).

    • Add Query.copy() and QueryThreadLocal to obtain a Query instance to use in different threads. .

    • Add relationCount query condition to match objects that have a certain number of related objects pointing to them. E.g. Customer_.orders.relationCount(2) will match all customers with two orders, Customer_.orders.relationCount(0)

    Using Sync? This release uses a new Sync protocol which improves efficiency. Reach out via your existing contact to check if any actions are required for your setup.

    V3.4.0 - 2022/10/18

    • Add findFirstId() and findUniqueId() to Query which just return the ID of a matching object instead of the full object.

    • Experimental support for setting a maximum data size via the maxDataSizeInKByte property when building a Store. This is different from the existing maxSizeInKByte property in that it is possible to remove data after reaching the limit and continue to use the database. See its documentation for more details.

    Using Sync? There is no Sync version for this release, please continue using version 3.2.1.

    V3.3.1 - 2022/09/05

    Note: V3.3.0 contains a bug preventing correct transformation of some classes, please use V3.3.1 instead.

    • Gradle plugin: use new transform API with Android Plugin 7.2.0 and newer. Builds should be slightly faster as only entity and cursor classes and only incremental changes are transformed.

    • Gradle plugin: improve detection of applied Android plugins, improve registration of byte-code transform for non-Android Java projects, add check for minimum supported version of Gradle.

    Using Sync? There is no Sync version for this release, please continue using version 3.2.1.

    V3.2.1 - 2022/07/05

    • Resolve an issue that prevented resources from getting cleaned up after closing BoxStore, causing the reference table to overflow when running many instrumentation tests on Android.

    • Plugin: support Kotlin 1.7.

    V3.2.0 - 2022/06/20

    • Query: throw IllegalStateException when query is closed instead of crashing the virtual machine.

    • BoxStore and Query now throw IllegalStateException when trying to subscribe but the store or query is closed already.

    • Various internal improvements including minor optimizations for binary size and performance.

    V3.1.3 - 2022/05/10

    • The Data Browser has been renamed to . Deprecated AndroidObjectBrowser, use Admin instead. AndroidObjectBrowser will be removed in a future release.

    • Windows: using a database directory path that contains unicode (UTF-8) characters does not longer create an additional, unused, directory with garbled characters.

    • Query: when using a negative offset or limit display a helpful error message.

    V3.1.2 - 2022/02/21

    This release only contains bug fixes for the Android library when used with ObjectBox for Dart/Flutter.

    V3.1.1 - 2022/01/26

    • Fix incorrect unique constraint violation if an entity contains at least two unique properties with a certain combination of non-unique indexes.

    • Data Browser/Admin: improved support when running multiple on the same host, but a different port (e.g. localhost:8090 and localhost:8091).

    V3.1.0 - 2021/12/15

    and code examples for the new flex properties and query conditions.

    • . Expanding on the string and flexible map support in 3.0.0, it is now possible to add a property using Object in Java or Any? in Kotlin. These "flex properties" allow to store values of various types like integers, floating point values, strings and byte arrays. Or lists and maps (using string keys) of those.

    • The containsElement query condition now matches keys of string map properties. It also matches string or integer elements of a Flex list.

    Kotlin

    • Add BoxStore.awaitCallInTx suspend function which wraps BoxStore.callInTx.

    Gradle plugin

    • Do not crash trying to add dependencies to Java desktop projects that only apply the Gradle application plugin.

    V3.0.0 - 2021/10/19

    2021/10/19: Released version 3.0.1, which contains a fix for Android Java projects.

    • is available that works similar to the Query API and makes it easier to create nested conditions. #201

    • For the existing Query API, String property conditions now require to explicitly specify case. See the documentation of for which one to choose (typically StringOrder.CASE_INSENSITIVE).

    • Subscriptions now publish results in serial instead of in parallel (using a single thread vs. multiple threads per publisher). Publishing in parallel could previously lead to outdated results getting delivered after the latest results. As a side-effect transformers now run in serial instead of in parallel as well (on the same single thread per publisher). #793

    New supported property types

    • String arrays (Java String[] and Kotlin Array<String>) and lists (Java List<String> and Kotlin MutableList<String>). Using the new containsElement("item") condition, it is also possible to query for entities where "item" is equal to one of the elements.

    • String maps (Java Map<String, String> or Kotlin MutableMap<String, String>

    Sync

    • The generated JSON model file no longer contains Java-specific flags that would lead to errors if used with Sync server.

    • Additional checks when calling client or server methods.

    V2.9.2-RC4 - 2021/08/19

    Note: this is a preview release. Future releases may add, change or remove APIs.

    • provides support for nested AND and OR conditions.

    • Subscriptions now publish results in serial instead of in parallel (using a single thread vs. multiple threads per publisher). Publishing in parallel could previously lead to outdated results getting delivered after the latest results. As a side-effect transformers now run in serial instead of in parallel as well (on the same single thread per publisher).

    • Add documentation that string property conditions ignore case by default. Point to using case-sensitive conditions for high-performance look-ups, e.g. .

    New supported property types

    When adding new properties, a converter is no longer necessary to store these types:

    • String arrays (Java String[] and Kotlin Array<String>). Using the new containsElement("item") condition, it is also possible to query for entities where "item" is equal to one of the array items.

    • String maps (Java Map<String, String> or Kotlin MutableMap<String, String>). Stored internally as a byte array using FlexBuffers.

    Sync

    • The generated JSON model file no longer contains Java-specific flags that would lead to errors if used with Sync server.

    • Additional checks when calling client or server methods.

    V2.9.1 - 2021/03/15

    This is the first release available on the Central repository (Sonatype OSSRH). Make sure to adjust your build.gradle files accordingly:

    Changes:

    • Javadoc for find(offset, limit) of Query is more concrete on how offset and limit work.

    • Javadoc for between conditions explicitly mentions it is inclusive of the two given values.

    • Sync: Instead of the same name and a Maven classifier, Sync artifacts now use a different name. E.g. objectbox-android:2.9.0:sync is replaced with objectbox-sync-android:2.9.1.

    V2.9.0 - 2021/02/16

    • Query: Add lessOrEqual and greaterOrEqual conditions for long, String, double and byte[] properties.

    • Support Java applications on ARMv7 and AArch64 devices.

      To use, add implementation "io.objectbox:objectbox-linux-armv7:$objectboxVersion or implementation "io.objectbox:objectbox-linux-arm64:$objectboxVersion to your dependencies. Otherwise the setup is identical with .

    V2.8.1 - 2020/11/10

    • Minor improvements to tooling.

    See the 2.8.0 release notes below for the latest changes.

    V2.8.0 - 2020/11/05

    • Added API.

    • Fixed "illegal reflective access" warning in the plugin.

    • The data browser notification is now silent by default, for quieter testing.

    • Updated and improved API documentation in various places (e.g. on how Query.findLazy() and Query.findLazyCached()

    V2.7.1 - 2020/08/19

    • Fix exception handling during BoxStoreBuilder.build() to allow retries. For example, after a FileCorruptException you could try to open the database again using the recently added usePreviousCommit() option.

    • Add PagesCorruptException as a special case of FileCorruptException.

    V2.7.0 - 2020/07/30

    • Several database store improvements forBoxStore and BoxStoreBuilder

      • New configuration options to open the database, e.g. a new read-only mode and using the previous data snapshot (second last commit) to potentially recover data.

      • Database validation. We got a GitHub report indicating that some specific devices ship with a broken file system. While this is not a general concern (file systems should not be broken), we decided to detect some typical problems and provide some options to deal with these.

    V2.6.0 - 2020/06/09

    • @DefaultValue("") annotation for properties to return an empty string instead of null. This is useful if a not-null property is added to an entity, but there are existing entities in the database that will return null for the new property.

    • objectbox-rxjava3. Also includes Kotlin extension functions to more easily obtain Rx types, e.g. use query.observable() to get an Observable.

    Changes since 2.6.0-RC (released on 2020/04/28):

    • Performance improvements with query links (aka "joins"). Note: the order of results has changed unless you explicitly specified properties to order by. Remember: you should not depend on any internal order. If you did, this is a good time to fix it.

    • objectbox-java no longer exposes the greenrobot-essentials and FlatBuffers dependencies to consuming projects.

    • Minor code improvements.

    V3.0.0-alpha2 - 2020/03/24

    Note: this is a preview release. Future releases may add, change or remove APIs.

    • Add Kotlin infix extension functions for creating conditions using the. See for examples.

    • The old Query API now also supports setting an alias after combining conditions using and() or or().

    • Add documentation that string property conditions ignore case by default. Point to using case-sensitive conditions for high-performance look-ups, e.g. .

    To use this release change the version of objectbox-gradle-plugin to 3.0.0-alpha2. The plugin now properly adds the preview version of objectbox-java to your dependencies.

    The objectbox-android, objectbox-linux, objectbox-macos and objectbox-windows artifacts shipping native code remain at version 2.5.1 as there have been no changes. If you explicitly include them, make sure to specify their version as 2.5.1.

    V3.0.0-alpha1 - 2020/03/09

    Note: this is a preview release. Future releases may add, change or remove APIs.

    • provides support for nested AND and OR conditions. See for examples and notable changes.

    • Subscriptions now publish results in serial instead of in parallel (using a single thread vs. multiple threads per publisher). Publishing in parallel could previously lead to outdated results getting delivered after the latest results. As a side-effect transformers now run in serial instead of in parallel as well (on the same single thread per publisher).

    • Turn on incremental annotation processing by default.

    To use this release change the version of objectbox-gradle-plugin to 3.0.0-alpha1 and add a dependency on objectbox-java version 3.0.0-alpha1.

    The objectbox-android, objectbox-linux, objectbox-macos and objectbox-windows artifacts shipping native code remain at version 2.5.1 as there have been no changes. However, if your project explicitly depends on them they will pull in version 2.5.1 of objectbox-java. Make sure to add an explicit dependency on of objectbox-java version 3.0.0-alpha1 as mentioned above.

    V2.5.1 - 2020/02/10

    • Support Android Gradle Plugin 3.6.0.

    • Support for incremental annotation processing. It is off by default. To turn it on set objectbox.incremental to true in build.gradle :

    V2.5.0 - 2019/12/12

    Important bug fix - please update asap if you are using N:M relations!

    • Fixed corner case for N:M ToMany (not the backlinks for ToOne) returning wrong results

    Improvements and New Features

    • Property queries compute sums and averages more precisely (improved algorithms and wider internal types)

    • Query adds "describe" methods to obtain useful debugging information

    • New method removeAllObjects() in BoxStore to clear the database of all data

    V2.4.1 - 2019/10/29

    • More helpful error messages if annotations can not be combined.

    • Improved documentation on various annotations.

    V2.4.0 - 2019/10/15

    Upgrade Notes

    • Android: the AAR libraries ship Java 8 bytecode. Your app will not build unless you upgrade com.android.tools.build:gradle to 3.2.1 or later.

    • Android: the and migrated from Android Support Libraries to Jetpack (AndroidX) Libraries. If you are using them the library will not work unless you make the following changes in your app:

      • Upgrade com.android.tools.build:gradle to 3.2.1 or later.

    Improvements & Fixes

    V2.4.0 - 2019/10/15

    • Class transformation works correctly if absolute path contains special characters.

    V2.4.0-RC - Release Candidate 2019/10/03

    • Box: add getRelationEntities, getRelationBacklinkEntities,getRelationIds and getRelationBacklinkIds to directly access relations without going through ToMany.

    • Box: add putBatched to put entities using a separate transaction for each batch.

    V2.3.4 - 2019/03/19

    • Avoid UnsatisfiedLinkError on Android devices that are not identifying as Android correctly

    • Fix displaying large objects in Object Browser 32 bit

    • Kotlin properties starting with "is" of any type are detected

    • Add objectbox-kotlin to dependencies if kotlin-android

    V2.3.3 - 2019/02/14

    • Fixed a bug introduced by V2.3.2 affecting older Android versions 4.3 and below

    V2.3.2 - 2019/02/04

    • Potential work around for UnsatisfiedLinkError probably caused by installation errors mostly in alternative app markets

    • Support for Android Gradle Plugin 3.3.0: resolves deprecated API usage warnings.

    V2.3.1 - 2019/01/08

    • Fixed a corner case for Box.getAll() after removeAll() to return a stale object if no objects are stored

    V2.3 - 2018/12/30

    Improvements & Fixes

    • Query improvements: findIds and LazyList also consider the order; offset and limit for findIds

    • Improved 32 bit support: Windows 32 version officially deployed, fixed a corner case crash

    • Property queries for a boolean property now allow sum()

    • Added Box.isEmpty()

    V2.2 - 2018/09/27

    Improvements & Fixes

    • Fix: the unique check for string properties had false positives resulting in UniqueViolationException. This occurs only in combination with IndexType.HASH (the default) when hashes actually collide. We advise to update immediately to the newest version if you are using hashed indexes.

    • The release of new made us change name of the JNI library

      for better distinction. This should not affect you unless you depended on that (internal) name.

    • Improved compatibility with class transformers like Jacoco

    V2.1 - 2018/08/16

    Minor Improvements & Fixes

    • Entity counts are now cached for better performance

    • Deprecated aggregate function were removed (deprecation in 1.4 with introduction of PropertyQuery)

    • Object browser hot fix: the hashed indexes introduced in 2.0 broke the object browser

    • Object browser fixes: filters with long ints, improved performance in the schema view

    V2.0 - 2018/07/25

    New Features/Improvements

    ObjectBox 2.0 introduces for String. Before, every index used the property value for all look-ups. Now, ObjectBox can also use a hash to build an index. Because String properties are typically taking more space than scalar values, ObjectBox switched the default index type to hash for strings.

    When migrating data from pre-2.0 ObjectBox versions, for String properties with a plain @Index annotation this will update the indexes automatically: the old value-based indexes will be deleted and the new hash-bashed indexes will be built.

    A side effect of this is that the database file might grow in the process. If you want to prevent this, you can instruct ObjectBox to keep using a value-based index for a String property by specifying the index type using @Index(type = IndexType.VALUE).

    Other changes:

    • Links and relation completeness and other features already announced in the 2.0 beta

    • Unique constraint for properties via

    • Support for char type (16 bit)

    • deployed in JCenter

    V2.0 beta – 2018/06/26

    New Features/Improvements

    • Query across relation bounds (aka "join"): queries just got much more powerful. For example, query for orders that have a customer with an address on "Sesame Street". Or all persons, who have a grand parent called "Alice".

    • : now ObjectBox is "relation complete" with a bi-directional many-to-many relation.

    • Query performance improvements: getting min/max values of indexed properties in constant time

    • Android: added (architecture components)

    V1.5.0 – 2018/04/17

    New Features/Improvements

    • Full support for : use full ObjectBox features in local tests

    • New count method optimized for a given maximum count

    • Gradle option to explicitly

    • Query condition startsWith now uses index if available for better performance

    Fixes

    • Fixed some static methods in BoxStore to ensure that the native lib is loaded

    • Internal optimizations for 64 bit devices

    • Some fixes for entities in the default package

    • Entity can be named Property

    V1.4.4 – 2018/03/08

    New Features/Improvements

    • Supply an initial database file using BoxStoreBuilder

    • Gradle plugin detects and configures dependencies

    • Improved Box.removeAll() performance for entities that have indexes or relations

    Fixes

    • Fixed converting from arrays in entities

    • Fixed @NameInDb

    • Fixed Gradle “androidTestCompile is obsolete” warning

    V1.4.3 – 2018/03/01

    New Features

    • macOS support: with Linux, Windows, and macOS, ObjectBox now supports all major desktop/server platforms. Use it for local unit tests or standalone Java applications.

    Fixes

    • Fixed BoxStore.close being stuck in rare scenarios

    • Fixed an issue with char properties in entities

    V1.4.2 – 2018/02/25

    Note: This release requires the or higher.

    Improvements

    • JCenter: we’ve moved the ObjectBox artifacts to the JCenter repository. This simplifies set up and improves accessibility (e.g. JCenter is not blocked from China).

    • Instant App support (only with Android Gradle Plugin 3.0.0 or higher)

    V1.4.1 – 2018/01/23

    Improvements

    • Added DbExceptionListener as a central place to listen for DB related exceptions

    • Minor improvements for ToMany and generated Cursor classes

    Fixes

    • ToMany: fixed handling of duplicate entries (e.g. fixes swap and reverse operations)

    • ToMany: fixed removal of non-persisted element for standalone relations

    V1.4.0 – 2018/01/11

    New Features

    • Property queries that return individual properties only (including distinct values, unique, null values, primitive result arrays or scalars)

    • Entity inheritance (non-polymorphic)

    • 50% size reduction of native libraries

    V1.3.4 – 2017/12/07

    Improvements

    • ToOne now implements equals() and hashCode() based on the targetId property

    • Android ABI x86_64 was added to the aar

    Fixes

    • ID verification does not complain about “resurrected” objects that were loaded, removed, and put again

    • Fixed setting Query parameters for Date type

    • Fixes for ObjectBox browser

    V1.3.3 (1.3.x) – 2017/12/04

    Please update to the latest version. We made important changes and fixes under the hood to make ObjectBox perform better, generally, and especially in concurrent scenarios. In addition, 1.3.x comes with several improvements for developers.

    Improvements

    • Object browser lets you download all entities as JSON

    • Object browser efficiency improvements: introduced streamed processing to reduce memory consumption and increase performance for large data sets

    • Improved transaction logging, e.g. numbered transactions and waiting times for write transactions

    Fixes

    • Fixes for concurrent setups (multi threaded, in live apps with up to 100 threads); internally we improved our testing automation and CI infrastructure significantly

    • Fix for sumDouble throwing an exception

    • Fixed ProGuard rule for ToOne

    V1.2.1 – 2017/11/10

    Improvements

    • Improved debug logging for transactions and queries: enable this using BoxStoreBuilder.debugFlags(…) with values from the class

    • Improved package selection for MyObjectBox if you use entities in multiple packages (please check if you need to adjust your imports after the update)

    • ObjectBox Browser’s UI is more compact and thus better usable on mobile devices

    Fixes

    • Fix for ObjectBoxLiveData firing twice

    V1.2.0 – 2017/10/31

    Compatibility note: We removed some Box.find methods, which were all tagged as @Temporary. Only the Property based ones remain (for now, also @Temporary).

    New Features

    • : Implements LiveData from Android Architecture Components

    • Object ID based methods for : getById, indexOfId, removeById

    • More robust Android app directory detection that works around Android bugs

    • Using the new official FlatBuffers Maven dependency (FlatBuffer is not anymore embedded in the artifact)

    Fixes

    • Fixed query order by float and double

    • Fixed an missing import if to-many relations referenced a entity in another package

    • Other minor fixes

    V1.1.0 – 2017/10/03

    New Features

    • Object Browser to view DB contents (Android)

    • Plain Java support to run ObjectBox on Windows and Linux

    • Added ToMany.hasA()/hasAll() to simplify query filters in Java

    • Sort query result via Comparator

    Fixes

    • Annotation processor detects boolean getters starting with “is”

    • Fixed a NPE with eager and findFirst() when there is no result

    V1.0.1 – 2017/09/10

    First bug fix release for .

    New Features

    • ToMany allows setting a Comparator to order the List (experimental)

    Fixes

    • Fix UID assignment process: use @Uid without value to see options (pin UID, reset/change)

    • Fix relation code generation for entities in different packages

    • Fix Kotlin extension functions in transformed (library) project

    • Fix ToOne access if field is inaccessible (e.g. in Kotlin data classes if they are part of constructor – lateinit were OK)

    V1.0.0 – 2017/09/04

    ObjectBox is out of beta! See our for details.

    New Features

    • Eager loading of relations via query builder

    • Java filters as query post-processing

    • Minor improvements like a new callInReadTx method and making Query.forEach breakable

    Fixes

    • Fixed two corner cases with queries

    V0.9.15 (beta) 2017/08/21 Hotfixes

    Fixes

    • Fixed: Android flavors in caused the model file (default.json) to be written into the wrong folder (inside the build folder) causing the build to fail

    • Fixed: failed builds if entity constructor parameters are of specific types

    V0.9.14 (beta) 2017/08/14 Standalone relations, new build tools

    For upgrade notes, please check the .

    New Features

    • No more in-place code generation: Java source code is all yours now. This is based on the new build tool chain introduced in 0.9.13. Thus Kotlin and Java share the same build system. The old Java-based plugin is still available (plugin ID “io.objectbox.legacy”) in this version.

    • “Standalone” to-many relations (without backing to-one properties/relations)

    • Gradle plugin tries to automatically add runtime dependencies (also (k)apt, but this does not always work!?)

    Fixes

    • Fixed the issue causing a “Illegal state: Tx destroyed/inactive, writeable cursor still available” error log

    V0.9.13 (beta) 2017/07/12 Kotlin Support

    New Features

    • Kotlin support (based on a new annotation processor)

    • Started “object-kotlin”, a sub-project for Kotlin extensions (tiny yet, let us know your ideas!)

    • BoxStoreBuilder: added maxReaders configuration

    • Get multiple entities by their IDs via Box methods (see get/getMap(Iterable))

    Fixes

    • Fixed ToOne without an explicit target ID property

    • Fixed type check of properties to allow ToMany (instead of List)

    • Fixed @Convert in combination with List

    • Fixed a race condition with cursor deletion when Java’s finalizer kicked in potentially resulting in a SIGSEGV

    V0.9.12 (beta) 2017/05/08 ToMany class

    • Update 2017/05/19: We just released 0.9.12.1 for the Gradle plugin (only), which fixes two problems with parsing of to-many relations.

    • Added the new list type ToMany which represents a to-many relation. A ToMany object will be automatically assigned to List types in the entity, eliminating a lot of generated code in the entity.

    • ToMany comes with change tracking: all changes (add/remove) are automatically applied to the DB when its hosting entity is persisted via put(). Thus, the list content is synced to the DB, e.g. their relationship status is updated and new entities are put.

    V0.9.11 (beta) 2017/04/25: Various improvements

    • Smarter to-one relations: if you put a new object that also has a new to-one relation object, the latter will also be put automatically.

    • Getters and setters for properties are now only generated if no direct field access is possible

    • JSR-305 annotations (@Nullable and others) to help the IDE find problems in your code

    V0.9.10 (beta) 2017/04/10: Bug Fixes and minor improvements

    New features and improvements

    • Breaking API: Replaced “uid” attribute of @Entity and @Property with @Uid annotation

    • An empty @Uid will retrieve the current UID automatically

    • Some minor efficency improvements for read transactions

    • Better DB resources clean up for internal thread pool

    Bug fixes

    • Better compatibility with Android Gradle plugin

    • Fixes for multithreaded reads of relation and index data

    • Fixed compilation error in generated sources for Entities without non-ID properties

    V0.9.9 (beta) 2017/03/07: Bug Fixes

    New features

    • Query.forEach() to iterate efficiently over query result objects

    Bug fixes

    • Various bug fixes

    V0.9.8 (beta) 2017/02/22: Going Reactive

    New features

    • Data observers with reactive extensions for transformations, thread scheduling, etc.

    • Optional RxJava 2 library

    • OR conditions for QueryBuilder allow more powerful queries

    Bug fixes

    • Fixed: Changing the order of an entity’s properties could cause errors in some cases

    • Fixed: Querying using relation IDs

    V0.9.7 (beta) 2017/02/10

    New features

    • LazyList returned by Query: another query option to defer getting actual objects until actually accessing them. This enables memory efficient iterations over large results. Also minimizes the time for a query to return. Note: LazyList cannot be combined with order specifications just yet.

    • QueryBuilder and Query now support Date and boolean types directly

    • QueryBuilder supports now a notIn opperator

    Breaking internal changes

    At this early point in the beta we decided to break backward compatibility. This allowed us to make important improvements without worrying about rather complex migrations of previous versions. We believe this was a special situation and future versions will likely be backward compatible although we cannot make promises. If you intend to publish an app with ObjectBox it’s a good idea to contact us before.

    • The internal data format was optimized to store data more compact. Previous database files are not compatible and should be deleted.

    • We improved some details how IDs are used in the meta model. This affects the model file, which is stored in your project directory (objectbox-models/default.json). Files created by previous versions should be deleted.

    V0.9.6 (first public beta release) 2017/01/24

    See

    Update and expand documentation on ToOne and ToMany.

    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 has changed to the GNU Affero General Public License (AGPL).

  • The maxDataSizeInKByte() option when building a store is ready for production use. This is different from the existing maxSizeInKByte() option in that it is possible to remove data after reaching the limit and continue to use the database. See its documentation for more details.
  • Admin will now print a warning when it does not have permission to show the Admin notification. When testing your app on a device with Android 13 or newer, developers should manually turn on notifications to make use of the Admin notification.

  • Added examples on how to use Kotlin's unsigned integer types to Custom types.

  • Restore compatibility with Kotlin 1.5. However, need to exclude kotlin-stdlib 1.8 from objectbox-kotlin as it includes classes previously in the -jdk7/-jdk8 libraries to avoid duplicate class file errors. So if not absolutely needed, we still recommend to use at least Kotlin 1.8.

  • Admin: the data table again displays all items of a page. #1135

  • The __cxa_pure_virtual crash should not occur anymore; if you get related exceptions, they should contain additional information to better diagnose this issue. Let us know details in #1131

  • Queries: all expected results are now returned when using a less-than or less-or-equal condition for a String property with index type VALUE. Reported via objectbox-dart#318

  • Queries: when combining multiple conditions with OR and adding a condition on a related entity ("link condition") the combined conditions are now properly applied. Reported via objectbox-dart#546

  • Some flags classes have moved to the new config package:

    • io.objectbox.DebugFlags is deprecated, use io.objectbox.config.DebugFlags instead.

    • io.objectbox.model.ValidateOnOpenMode is deprecated, use io.objectbox.config.ValidateOnOpenModePages instead.

  • (or their Kotlin counterparts, e.g. FloatArray) without a converter.

    A simple example is a shape entity that stores a palette of RGB colors:

    This can also be useful to store vector embeddings produced by machine learning, e.g.:

  • Fix incorrect Cursor code getting generated when using @Convert to convert to a String array.

  • The io.objectbox.sync plugin now also automatically adds a Sync-enabled JNI library on macOS and Windows (previously on Linux x64 only; still need to add manually for Linux on ARM).

  • will match all customers with no associated order. This can be useful to find objects where the relation was dissolved, e.g. after the related object was removed.
  • Allow using a relation target ID property with a property query. E.g. query.property(Order_.customerId) will map results to the ID of the customer of an order. #1028

  • Add docs on DbFullException about why it occurs and how to handle it.

  • Do not fail to transform an entity class that contains a transient relation field when using Android Gradle Plugin 7.1 or lower.

  • Restore compatibility for Android projects using Gradle 6.1. The minimum supported version for Gradle is 6.1 and for the Android Gradle Plugin 3.4. This should make it easier for older projects to update to the latest version of ObjectBox.

  • Fix a crash when querying a value-based index (e.g. @Index(type = IndexType.VALUE)) on Android 32-bit ARM devices. #1105

  • Various small improvements to the native libraries.

  • Processor: do not crash, but error if ToOne/ToMany type arguments are not supplied (e.g. ToOne instead of ToOne<Entity>).

    New containsKeyValue query condition to match key/value combinations of string map and Flex map properties containing strings and integers. Also added matching Query.setParameters overload.

  • Add ProGuard/R8 rule to not warn about SuppressFBWarnings annotation. #1011

  • Add more detailed error message when loading the native library fails on Android. #1024

  • Data browser: byte arrays are now correctly displayed in Base64 encoding. #1033

  • Support annotating a single property with @Unique(onConflict = ConflictStrategy.REPLACE) to replace an existing Object if a conflict occurs when doing a put. #509

  • Support @Unsigned to indicate that values of an integer property (e.g. Integer and Long in Java) should be treated as unsigned when doing queries or creating indexes.

  • Store time in nanoseconds using the new @Type annotation for compatibility with other ObjectBox language bindings:

  • Package FlatBuffers version into library to avoid conflicts with apps or other libraries using FlatBuffers. #894

  • Kotlin: add Flow extension functions for BoxStore and Query. #900

  • Data browser: display query results if a property has a NaN value. #984

  • Android 12: support using Data Browser if targeting Android 12 (SDK 31). #1007

  • ). Stored internally as a byte array using FlexBuffers.
  • Flexible maps:

    • map keys must all have the same type,

    • map keys or values must not be null,

    • map values must be one of the supported database type, or a list of them (e.g. String, Boolean, Integer, Double, byte array...).

  • Support annotating a single property with @Unique(onConflict = ConflictStrategy.REPLACE) to replace an existing Object if a conflict occurs when doing a put. #509
  • Support @Unsigned to indicate that values of an integer property (e.g. Integer and Long in Java) should be treated as unsigned when doing queries or creating indexes. See the Javadoc of the annotation for more details.

  • Store time in nanoseconds by annotating a Long property with @Type(DatabaseType.DateNano).

  • Package FlatBuffers version into library to avoid conflicts if your code uses FlatBuffers as well. #894

  • Kotlin: add Flow extension functions for BoxStore and Query. #900

  • Data browser: display query results if a property has a NaN value. #984

  • Resolve rare ClassNotFoundException: kotlin.text.Charsets when running processor. #946

  • Ensure Query setParameters works if running the x86 library on x64 devices (which could happen if ABI filters were set up incorrectly). #927

  • work with
    LazyList
    ).
  • Print full name and link to element for @Index and @Id errors. #902

  • Explicitly allow to remove a DbExceptionListener by accepting null values for BoxStore.setDbExceptionListener(listener).

  • DbExceptionListener is called more robustly.

    Get the size on disk

  • Add an efficient check if an object exist in a Box via contains(id).

  • Android improvements

    • Resolve Android Studio Build Analyzer warning about a prepare tasks not specifying outputs.

    • Data Browser drawables are no longer packaged in the regular Android library. GitHub #857

  • Fixes for one-to-many relations, e.g. allow removing both entity classes of a one-to-many relation. GitHub #859

  • The annotation processor is incremental by default. GH#620
  • Fix error handling if ObjectBox can't create a Java entity (the proper exception is now thrown).

  • Support setting an alias after combining conditions using and() or or(). GH#83

  • Turn on incremental annotation processing by default. GH#620

  • Add documentation that string property conditions ignore case by default. Point to using case-sensitive conditions for high-performance look-ups, e.g. when using string UIDs.

  • Repository Artifacts are signed once again.

  • Java's String[] and Kotlin's Array<String> are now a supported database type. A converter is no longer necessary to store these types. Using the arrayProperty.equal("item") condition, it is possible to query for entities where "item" is equal to one of the array items.

  • Support @Unsigned to indicate that values of an integer property (e.g. Integer and Long in Java) should be treated as unsigned when doing queries or creating indexes. See the Javadoc of the annotation for more details.

  • Add new library to support RxJava 3, objectbox-rxjava3. In addition objectbox-kotlin adds extension functions to more easily obtain Rx types, e.g. use query.observable() to get an Observable. GH#839

  • Upgrade compileSdkVersion to 28 or later.
  • Update your app to use Jetpack (AndroidX); follow the instructions in Migrating to AndroidX.

  • Note: this version requires backwards-incompatible changes to the generated MyObjectBox file. Make sure to rebuild your project before running your app so the MyObjectBox file is re-generated.

  • Box.removeByKeys() is now deprecated; use removeByIds() instead.
  • Query: fixed performance regressions introduced in version 2.3 on 32 bit devices in combination with ordered results

  • Fixed removing a relation and the related entity class. GH#490

  • Resolved issue to enable query conditions on the target ID property of a ToOne relation. GH#537

  • Box.getAll always returns a mutable list. GH#685

  • Do not overwrite existing objectbox-java or objectbox-kotlin dependency. GH#693

  • Resolved a corner case build time crash when parsing package elements. GH#698

  • When trying to find an appropriate get-method for a property, also check if the return type matches the property type. GH#720

  • Explicitly display an error if two entities with the same name are detected. GH#744

  • The code in MyObjectBox is split up by entity to make it less likely to run into the Java method size limit when using many @Entity classes. GH#750

  • Query: improved performance for ordered results with a limit. GH#769

  • Query: throw if a filter is used incorrectly with count or remove. GH#771

  • Documentation and internal improvements.

  • plugin is applied (previously only for
    kotlin
    plugin)
  • @BaseEntity classes can be generic

  • Supporting older Linux distributions (now starting at e.g. Ubuntu 16.04 instead of 18.04)

  • Fix for a corner case with Box.count() when using a maximum

  • Minor improvements to the ObjectBox code generator

  • Android: set extractNativeLibs to false to avoid issues with extracting the native library

  • Fixed query links for M:N backlinks

  • Improved error messages for the build tools

  • The Object Browser AAR now includes the required Android permissions

  • NPE fix in ToOne

  • Added a specific NonUniqueResultException if a query did not return an expected unique result

  • Rework of Query APIs: type safe properties (property now knows its owning entity)

  • Allow query conditions of links using properties (without parameter alias)

  • Query performance improvements when using order

  • Property based count: query for non-null or unique occurrences of entity properties (non-null and unique)

  • Additional query conditions for strings: "greater than", "less than", "in"

  • Added query conditions for byte arrays

  • Set query parameters for "in" condition (int[] and long[])

  • Kotlin extensions: more Kotlin fun with ObjectBox KTX

  • Query parameters aliases: helps setting query parameters in complex scenarios (e.g. for properties of linked entities)

  • Improved query parameter verification

  • Many internal improvements to keep us going fast in the future

  • , no longer conflicts with ObjectBox Property class
  • Property queries for strings crashed on some Android devices if there were more than 512 results

  • Object Browser uses less threads

  • Object Browser now displays negative int/long values correctly

  • Changes to relations object in constructors were overwritten when constructors delegated to other constructors

  • Closing the store (e.g. for tests, an app should just leave it open) will wait for any ongoing write transaction to finish

  • Two additional overloads for static BoxStore.deleteAllFiles()

  • Added automatic retries for read transactions; also configurable for queries

  • UI improvements for ObjectBox browser

  • Other minor improvements

  • Improved error messages on build errors

  • Internal clean up, dropping legacy plugin

  • Improved error reporting

    ToOne and ToMany are now serializable (which does not imply serializing is a good idea)

  • ObjectBox may now opt to construct entities using the no-args constructor if the all-args constructor is absent

  • Prevents opening two BoxStores for the same DB file, which may have side effects that are hard to debug

  • Various minor and internal improvements and fixes

  • Fixed a leak with potentially occurring with indexes

    Streamlined annotations (breaking API changes): @Generated(hash = 123) becomes @Generated(123), @Property was removed, @NameInDb replaces attributes in @Entity and the former @Property, Backlinking to-many relations require @Backlink (only), @Relation is now only used for to-one relations (and is subject to change in the next version)

    @Uid(-1) will reassign IDs to simplify some migrations (docs will follow soon)
  • No more getter for ToOne objects in favor of direct field access

  • Quite a few internal improvements (evolved EntityInfo meta info object, etc.)

  • put() now uses entity fields directly unless they are private (can be more efficient than calling getters)
    Vector Search example
    approximate nearest neighbor (ANN) search
    the new query APIs
    Vector Search
    Vector Search documentation
    #1154
    #1143
    #1099
    Learn more about re-using queries
    #1071
    #1078
    #1080
    #1085
    #1081
    ObjectBox Admin
    Read the blog post with more details
    Support Flex properties
    A new Query API
    ObjectBox for Dart/Flutter
    StringOrder
    A new experimental Query API
    #201
    #793
    when using string UIDs
    #657
    Java Desktop Apps
    Sync
    Sync
    #903
    GH#157
    RxJava 3 support library
    GH#83
    new Query API
    the documentation
    GH#834
    when using string UIDs
    A new Query API
    the documentation
    GH#201
    GH#793
    GH#620
    GH#817
    GH#620
    ObjectBox LiveData
    Paging integration
    GH#135
    ObjectBox C API
    index types
    @Unique annotation
    RX lib
    using links
    Backlinks for to-many relations
    Paging library support
    Android local tests
    define the package for MyObjectBox
    plain Java projects
    Android Gradle Plugin 3.0.0
    Flag for query parameter logging
    DebugFlags
    ObjectBoxLiveData
    ToMany
    ObjectBox 1.0
    announcement blog post
    announcement post
    ObjectBox Announcement
    #906
    @Entity
    data class Example(
            @Id
            var id: Long = 0,
            @Unique(onConflict = ConflictStrategy.REPLACE)
            var uniqueKey: String? = null
    )
    @Type(DatabaseType.DateNano)
    var timeInNanos: Long;
    @Entity
    public class City {
    
        @HnswIndex(dimensions = 2)
        float[] location;
        
    }
    final float[] madrid = {40.416775F, -3.703790F};
    final Query<City> query = box
            .query(City_.location.nearestNeighbors(madrid, 2))
            .build();
    final City closest = query.findWithScores().get(0).get();
    store = MyObjectBox.builder()
            .androidContext(context)
            .inMemory("test-db")
            .build();
    // equal AND (less OR oneOf)
    val query = box.query(
          User_.firstName equal "Joe"
                  and (User_.age less 12
                  or (User_.stamp oneOf longArrayOf(1012))))
          .order(User_.age)
          .build()
    // Replace String conditions like
    query().equal(User_.firstName, "Joe")
    // With the one accepting a StringOrder
    query().equal(User_.firstName, "Joe", StringOrder.CASE_INSENSITIVE)
    @Entity
    data class Example(
            @Id
            var id: Long = 0,
            var stringArray: Array<String>? = null,
            var stringMap: MutableMap<String, String>? = null
    )
    
    // matches [“first”, “second”, “third”]
    box.query(Example_.stringArray.containsElement(“second”)).build()
    repositories {
        mavenCentral()
    }
    buildscript {
        dependencies {
            classpath "io.objectbox:objectbox-gradle-plugin:3.0.0-alpha2"
        }
    }
    
    dependencies {
        // Artifacts with native code remain at 2.5.1.
        implementation "io.objectbox:objectbox-android:2.5.1"
    }
    buildscript {
        dependencies {
            classpath "io.objectbox:objectbox-gradle-plugin:3.0.0-alpha1"
        }
    }
    
    dependencies {
        implementation "io.objectbox:objectbox-java:3.0.0-alpha1"
        // Artifacts with native code remain at 2.5.1.
        implementation "io.objectbox:objectbox-android:2.5.1"
    }
    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [ "objectbox.incremental" : "true" ]
                }
            }
        }
    }
    @Entity
    public class Shape {
    
        @Id public long id;
    
        // An array of RGB color values that are used by this shape.
        public int[] palette;
    
    }
    
    // Find all shapes that use red in their palette
    try (Query<Shape> query = store.boxFor(Shape.class)
            .query(Shape_.palette.equal(0xFF0000))
            .build()) {
            query.findIds();
    }
    @Entity
    public class ImageEmbedding {
    
        @Id public long id;
    
        // Link to the actual image, e.g. on Cloud storage
        public String url;
    
        // The coordinates computed for this image (vector embedding)
        public float[] coordinates;
    
    }