Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Guides and documentation for advanced use cases of ObjectBox.
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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.
💚 Please share your feedback! 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 Java & Kotlin or Dart/Flutter) 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 ⭐
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.
How to create ObjectBox local unit tests for Android projects.
ObjectBox supports local unit tests. This gives you the full ObjectBox functionality for running super fast test directly on your development machine.
On Android, unit tests 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 Building Local Unit Tests. Read along to learn how to use ObjectBox in your local unit tests.
This page also applies to writing unit tests for desktop projects.
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.
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.
It’s usually a good idea to extract the setup and tear down methods into a base class for your tests. E.g.:
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.
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 GitHub issue 605.
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.
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 Play Core library 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 how we updated our example app to use the Play Core library detection.
If the Play Core library should not be used, there are two alternatives:
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 how we added this to our Android app example.
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: Android Developers
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 ReLinker 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:
Multidex supports two file formats to keep files. 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.
To enable debug logs for ReLinker you can pass a custom ReLinkerInstance
when building BoxStore
:
LiveData is an observable data holder class. Learn to use ObjectBox database with LiveData from Android Architecture Components.
Since 1.2.0. Have a look at the example project on GitHub.
As an alternative to ObjectBox’ data observers and reactive queries, you can opt for the LiveData approach supplied by Android Architecture Components. ObjectBox comes with ObjectBoxLiveData
, a class that can be used inside your ViewModel 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 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 added and set up the Paging library in your project.
Within your ViewModel
, similar to creating a LiveData
directly, you first build your ObjectBox query. 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 Paging library documentation for details about this.
Have a look at the ObjectBox Architecture Components example code.
Check out ObjectBox support for LiveData.
Learn how to build queries.
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:
Define your data model along with a vector index,
Insert your data/vectors,
Search for nearest neighbors.
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.
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.
So, this is what a City data class with an HNSW index definition can look like:
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.
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.
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.
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
).
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.
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.
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.
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.
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:
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
Quantization and support for non-float vectors
ObjectBox Vector Search offers significant advantages by combining the capabilities of a vector search engine with the robustness of a full-featured database:
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.
Instant Readiness
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.
With ObjectBox Vector Search, you get a powerful, flexible, and scalable solution tailored for modern applications where data relationships matter.
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.
If you are interested in scientific details, check out the .
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.
For C++, you define the data model using FlatBuffer schema files (see the for details):
Performance note: for inserting multiple objects at once, wrap a around the put
commands.
(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.
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.
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.
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.
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.
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 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 Store
class provides read_tx
and write_tx
methods for creating read/write transactions which should be called in a with
statement:
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.put
calls) in one transaction.
Consider this example:
Do you see what’s wrong with that code? There is an implicit transaction for each user which is very inefficient, especially for a high number of objects. It is much more efficient to use one of the put overloads to store all users at once:
Much better! If you have 1,000 users, the latter example uses a single transaction to store all users. The first code example uses 1,000 (!) implicit transactions, causing a massive slow down.
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).
ObjectBox gives developers Multiversion concurrency control (MVCC) 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.
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”.
How to observe box and query changes using ObjectBox with Java or Dart, how to integrate with RxJava.
On this page:
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.
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
once updated query results are in, they are propagated to the observer
the observer is called on Android’s main thread
Now, let’s dive into the details.
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).
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.
ObjectBox let’s you build queries 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 subscribe()-method documentation for more details.
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()
.
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.
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.
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.
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.
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()
.
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.
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()
.
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()
method.
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 GitHub.
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:
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.
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.
See the Getting Started page on how to set up your project, add entities and use the ObjectBox APIs.
There are example command line apps available in our examples repository.
The setup and writing tests is identical to writing unit tests that run on the local JVM for Android, see Android Local Unit Tests.
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 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.
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. Sealed classes 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:
When defining relations in Kotlin, keep in mind that relation properties must be var
. Otherwise they can not be initialized as described in the relations docs. 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 https://docs.objectbox.io/relations#initialization-magic 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) are equal and have the same hash code. 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.
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:
The new Query API makes below extensions functions unnecessary.
Build a query:
Use the in filter of a query:
Modify a ToMany:
Get a Flow from a Box or Query subscription (behind the scenes this is based on a Data Observer):
Something missing? Let us know what other extension functions you want us to add.
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:
Check out the Kotlin example on GitHub.
Continue with Getting Started.
ObjectBox Relations: Learn how to create and update to-one and to-many relations between entities in ObjectBox and improve performance.
Prefer to dive right into code? Check out our
Kotlin Android example app using relations,
Java relations playground Android 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.
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 Initialization Magic.
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 updating relations.
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 updating ToOne 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.
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 like the ToOne property appended with Id.
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:
You can change the name of the expected target ID property by adding the @TargetIdProperty(String) annotation to a ToOne.
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.
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 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.
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 Initialization Magic.
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) are equal and have the same hash code. 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 updating relations.
Note: if your entities use manually assigned IDs with @Id(assignable = true)
the above will not work. See below about updating ToMany 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:
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 Initialization Magic.
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 updating relations.
Note: if your entities use manually assigned IDs with @Id(assignable = true)
the above will not work. See below about updating ToMany 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:
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 Initialization Magic.
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.
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:
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.
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.
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 documentation about IDs for background information.
The ToMany
relation class is based on a standard List
with added change tracking for Objects. As mentioned above, 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 One-to-Many and Many-to-Many 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 documentation about IDs for the background information.
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
For each ToOne
you could have a matching ToMany
on the other side of the relation (backlink)
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 Initialization Magic.
This lets you navigate a tree nodes parent and children:
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 Getting Started 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.
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.
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.
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.
When the user taps a note, it is deleted. The Box
provides remove()
to achieve this:
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.
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.
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.
🌿 Learn how to build a Food Sharing app with ObjectBox in Flutter/Dart
Discover ObjectBox: The Lightning-Fast Mobile Database for Persistent Object Storage. Streamline Your Workflow, Eliminate Repetitive Tasks, and Enjoy a User-Friendly Data Interface.
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 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.
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, see the @Id annotation docs 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.
Support for many property types is already built-in, but almost any type can be stored with a converter.
For more details about entities, like how to create an index or a relation, check the Entity Annotations page.
You can also learn more about the ObjectBox model.
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 Data Model Updates.
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 Advanced Setup page.
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).
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 Advanced Setup.
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 when renaming entities or properties or to resolve conflicts when two of your developers make changes at the same time.
BoxStore (Java) or Store (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 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:
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 Flutter: read & write files 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 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.
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.
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 BoxStoreBuilder and for Dart the Store documentation. (Python APIs will be published soon)
The Box class 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 queries 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 Java or Dart.
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 (API reference): run a callback with multiple database operations within a write or read transaction in the background without blocking the user interface. Can return results.
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.
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 how to switch to self-assigned IDs and what side effects apply.
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 Object IDs.
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 transaction documentation.
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 the documentation and the example. Contact us if you have any questions!
Check out the ObjectBox example projects on GitHub.
Learn how to write unit tests.
To enable debug mode and for advanced use cases, see the Advanced Setup page.
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.
The ObjectBox Admin web app allows you to
view data objects and schema of your database inside a regular web browser,
display additional information,
and download objects in JSON format.
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.
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.
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
.
Windows PowerShell/Command Prompt
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.
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.
For database files stored on a Windows file system (NTFS), to see changes to the database, Admin needs to be restarted.
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:
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.
To open the web app on your development machine find the Admin URL log message as noted above.
Then open the web app URL in a web browser on your dev machine.
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.
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.
Additional configuration options when creating an ObjectBox database.
This page contains:
To then change the default behavior of the ObjectBox plugin and processor read on for advanced setup options.
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:
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.
In your app’s Gradle build script, the following processor options, explained below, are available:
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.
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.
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:
ObjectBox can help you migrate from greenDAO by generating classes with a greenDAO-like API.
To customize the directory (relative to the package root) where the files generated by ObjectBox are written, add the following to your pubspec.yaml
:
How to inherit properties from 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:
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.
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).
Answers to questions specific to ObjectBox for Java and Dart
Yes. ObjectBox comes with strong relation support and offers features like “eager loading” for optimal performance.
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.
No. The objects you get from ObjectBox are POJOs (plain objects). You are safe to pass them around in threads.
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.
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.
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!
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.
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
:
More importantly, ObjectBox adds little to the APK method count since it’s mostly written in native code.
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).
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.
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.
If you believe to have found a bug or missing feature, please create an issue.
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.
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.
ObjectBox assigns meta model IDs sequentially (1, 2, 3, 4, …) and keeps track of the last used ID to prevent ID collisions.
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.
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.
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.
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.
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.
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.
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.
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: do a text search for the UID and check if the ID part is correct for all UID occurrences
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.
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.
Although this is technically the changelog for Java, this is also a good reference of what changed in the ObjectBox C++ core.
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).
Revert deprecation of Box.query()
, it is still useful for queries without any condition.
Update and expand documentation on ToOne
and ToMany
.
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:
BoxStore: deprecated BoxStore.sizeOnDisk()
. Instead use one of the new APIs to determine the size of a database:
BoxStore.getDbSize()
which for a file-based database returns the file size and for an in-memory database returns the approximately used memory,
BoxStore.getDbSizeOnDisk()
which only returns a non-zero size for a file-based database.
Query: add properly named setParameter(prop, value)
methods that only accept a single parameter value, deprecated the old setParameters(prop, value)
variants.
Sync: add SyncCredentials.userAndPassword(user, password)
.
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.
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.
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.
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.
Admin: integer and floating point arrays introduced with the previous release are now nicely displayed and collapsed if long.
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.
Support for integer and floating point arrays: store
short[]
, char[]
, int[]
, long[]
and
float[]
and double[]
(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.
Add docs to DbSchemaException
on how to resolve its typical causes.
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 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)
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.
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.
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.
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.
Various small improvements to the native libraries.
Using Sync? There is no Sync version for this release, please continue using version 3.2.1.
Note: V3.3.0 contains a bug preventing correct transformation of some classes, please use V3.3.1 instead.
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.
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.
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.
Processor: do not crash, but error if ToOne/ToMany type arguments are not supplied (e.g. ToOne
instead of ToOne<Entity>
).
This release only contains bug fixes for the Android library when used with ObjectBox for Dart/Flutter.
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
).
The containsElement
query condition now matches keys of string map properties. It also matches string or integer elements of a Flex list.
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
Add BoxStore.awaitCallInTx
suspend function which wraps BoxStore.callInTx
.
Do not crash trying to add dependencies to Java desktop projects that only apply the Gradle application
plugin.
2021/10/19: Released version 3.0.1, which contains a fix for Android Java projects.
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
Support annotating a single property with @Unique(onConflict = ConflictStrategy.REPLACE)
to replace an existing Object if a conflict occurs when doing a put. #509
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
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>
). 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...).
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.
Note: this is a preview release. Future releases may add, change or remove APIs.
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)
.
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.
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
.
Query: Add lessOrEqual
and greaterOrEqual
conditions for long, String, double and byte[] properties.
See the 2.8.0 release notes below for the latest changes.
Fixed "illegal reflective access" warning in the plugin.
Explicitly allow to remove a DbExceptionListener
by accepting null values for BoxStore.setDbExceptionListener(listener)
.
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
.
DbExceptionListener
is called more robustly.
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.
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.
Fix error handling if ObjectBox can't create a Java entity (the proper exception is now thrown).
Repository Artifacts are signed once again.
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.
Note: this is a preview release. Future releases may add, change or remove APIs.
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.
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
.
Note: this is a preview release. Future releases may add, change or remove APIs.
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.
Fixed corner case for N:M ToMany (not the backlinks for ToOne) returning wrong results
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
More helpful error messages if annotations can not be combined.
Improved documentation on various annotations.
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.
Upgrade com.android.tools.build:gradle to 3.2.1 or later.
Upgrade compileSdkVersion to 28 or later.
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.
V2.4.0 - 2019/10/15
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.
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
Documentation and internal improvements.
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
plugin is applied (previously only for kotlin
plugin)
@BaseEntity classes can be generic
Fixed a bug introduced by V2.3.2 affecting older Android versions 4.3 and below
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.
Fixed a corner case for Box.getAll() after removeAll() to return a stale object if no objects are stored
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()
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
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.
for better distinction. This should not affect you unless you depended on that (internal) name.
Improved compatibility with class transformers like Jacoco
Fixed query links for M:N backlinks
Improved error messages for the build tools
The Object Browser AAR now includes the required Android permissions
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
NPE fix in ToOne
Added a specific NonUniqueResultException if a query did not return an expected unique result
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
Support for char type (16 bit)
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
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[])
Query performance improvements: getting min/max values of indexed properties in constant time
Improved query parameter verification
Many internal improvements to keep us going fast in the future
How to rename entities and properties, change property types in ObjectBox.
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.
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.
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:
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.
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.
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:
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.
Which types are supported by default in ObjectBox, how to store types that are not, recommendations for storing enums.
With ObjectBox you can store pretty much any type (class), given that it can be converted to any of the built-in types.
ObjectBox can store the following built-in types without a converter:
Only Java/Kotlin
ObjectBox supports properties where the type is not known at compile time using Object
in Java or Any?
in Kotlin. These "flex properties" can store types like integers, floating point values, strings and byte arrays. Or lists and maps (using string keys) of those. In the database these properties are stored as byte arrays.
To override the default converter chosen by ObjectBox, use @Convert
. For example to use another built-in FlexObjectConverter
subclass:
You can also write a custom converter like shown below.
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:
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.
ObjectBox (Java, Dart) has built-in support for String lists. ObjectBox for Java also has built-in support for String arrays.
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.
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)
.
ObjectBox database persistence for Java is based on objects for object-oriented programming instead of SQL. Learn how to persist entities with entity annotations in this tutorial section.
ObjectBox is a database that persists objects. For a clear distinction, we sometimes call those persistable objects 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:
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.
ID properties are unique and indexed by default.
ObjectBox needs to access the data of your entity’s properties (e.g. in the generated code). You have two options:
Make sure properties do not have private visibility.
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.
ObjectBox can store almost any type (class) of property as long as it can be converted to one of the built-in types. See the dedicated page for details:
@Transient
marks properties that should not be persisted. In Java static
or transient
properties will also not be persisted.
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.
@NameInDb only works with inline constants to specify a column name.
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
@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
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.
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.
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.
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.
To enable nearest neighbor search, a special index type for vector properties is available:
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.
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.
Explanation of Object IDs and how they are used and assigned in ObjectBox.
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.
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.
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.
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.
If your code needs to assign IDs by itself you can change the @Id
annotation to:
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.
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.
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")
.
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 1970 and whose last name starts with “O”:
To nest conditions pass a combined condition to and()
or or()
:
one_of is not yet available in Python.
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)
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.
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()
to filter for values that match any in the given array,
startsWith()
, endsWith()
and contains()
for extended String filtering.
See the API for a full list:
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.
For example, to query an Order
entity with a date
field to find all orders in 2023:
A special condition is available for vector properties with an HNSW index. See the dedicated page for details:
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.
Once the query is created, it allows various operations, which we will explore in the following sub sections.
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.
To remove all objects matching a query, call query.remove()
.
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:
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.
Only Java/Kotlin
Only Dart
Instead of reading the whole result (list of objects) using find()
you can stream it using stream()
:
To learn how to observe or listen to changes to the results of a query, see the data observers page:
For example, instead of getting all User
s, 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.
By default, null values are not returned. However, you can specify a replacement value to return if a property is null:
The property query can also only return distinct values:
min()
/ minDouble()
: Finds the minimum value for the given property over all objects matching the query.
max()
/ maxDouble()
: Finds the maximum value.
sum()
/ 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.
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:
Only Java/Kotlin
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 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:
Narrow down results using standard database conditions to a reasonable number (use QueryBuilder to get “candidates”)
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.
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)
To see what query is actually executed by ObjectBox:
Then in your console (or logcat on Android) you will see log output like:
Prefer to look at example code? Check out our examples repository.
ObjectBox tools and dependencies are available on the Maven Central repository.
To add ObjectBox to your Android project, follow these steps:
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:
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 FAQ and Troubleshooting pages.
Then do "Sync Project with Gradle Files" in Android Studio so the Gradle plugin automatically adds the required ObjectBox libraries and code generation tasks.
Your project can now use ObjectBox, continue by defining entity classes.
Prefer to look at example code? Check out our examples repository.
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
targeting at least Java 8
built with Gradle or Maven
ObjectBox tools and dependencies are available on the Maven Central repository.
To set up a Maven project, see the README of the Java Maven example project.
The instructions assume a multi-project build is used.
Open the Gradle build script of your root project and
add a global variable to store the common version of ObjectBox dependencies and
add the ObjectBox Gradle plugin:
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.
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.
Your project can now use ObjectBox, continue by defining entity classes.
On Windows you might have to install the latest Microsoft Visual C++ Redistributable package (X64) to use ObjectBox.
You can watch these video tutorials as well 😀:
Prefer to look at example code? Check out our examples directory.
To add ObjectBox to your Flutter project:
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.
This should add lines like this to your pubspec.yaml
:
If you added the above lines manually, then install the packages with flutter pub get
.
For Android increase the NDK version:
For all macOS apps need to target macOS 10.15: in Podfile
change the platform and in the Runner.xcodeproj/poject.pbxproj
file update MACOSX_DEPLOYMENT_TARGET
.
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.
For Android using Flutter 3.19 or older and the ObjectBox Sync-enabled library: increase minSdkVersion to at least 21.
For iOS using Flutter 3.0 or older: increase the deployment target in Xcode to iOS 12 and, under Architectures, replace ${ARCHS_STANDARD}
with arm64
(or $ARCHS_STANDARD_64_BIT
).
Prefer to look at example code? Check out our examples directory.
Run these commands:
This should add lines like this to your pubspec.yaml
:
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).
Prefer to look at example code? Check out our examples directory.
ObjectBox for Python is available via PyPI: Stable Version (4.0.0):
Admin comes in two variants: as a standalone and embedded in the library.
To run Admin on a desktop operating system, e.g. your development machine, you can launch ObjectBox Admin instantaneously using the official objectboxio/admin
.
This requires a running Docker Engine or Docker Desktop. If not done, install and start (Linux, Windows/WSL2) or (Windows, macOS) capable of running a Linux/x86_64 image.
Note: Make sure Docker Desktop runs .
Then, on your dev machine, (or whichever you like) to that port of your device. If the default port 8090 is used, the command looks like this:
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 .
This page assumes you have to your project.
On Windows you might have to install the latest to use the ObjectBox DLL.
See the on how to enable and use this feature.
ObjectBox comes with full including data classes. And yes, it supports .
It depends. Internally and in , 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.
ObjectBox supports iOS 12 or newer on 64-bit devices only. An iOS library is available for or 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 .
Yes. You can run the ObjectBox database on any IoT device that runs Linux. We also offer Go and C APIs. Check our and see how easy it is to sync data across platforms in real time with ObjectBox.
If you only do a rename on the language level, ObjectBox will by default remove the old and add a new entity/property. .
The Google Play download size increases by around 2.3 MB (checked for ObjectBox 3.0.0) as a native library for each is packaged. If you split by ABI or use it only increases around 0.6 MB.
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
. 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). . It also avoids issues that might occur when extracting the libraries.
This is also in any case when building a Flutter app with minimum API level 23 (Android 6.0).
For Java, there is an experimental initialDbFile()
method when building BoxStore. if this is useful!
Questions that apply to all supported platforms and languages are answered in the .
For Java/Kotlin:
For Flutter/Dart:
If you have a usage question regarding ObjectBox, please post on Stack Overflow.
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.
For example, look at a file from the :
Also check out this for hands-on information on renaming and resetting.
a database file with a different model getting restored by or getting .
Examples: added that demonstrates how to perform on-device .
Add note on old query API methods of QueryBuilder
that they are not recommended for new projects. Use instead.
ObjectBox now supports to enable efficient similarity searches.
For an introduction to Vector Search, more details and other supported languages see the .
Gradle plugin: the license of the has changed to the GNU Affero General Public License (AGPL).
Added examples on how to use Kotlin's unsigned integer types to .
Throw an exception instead of crashing when trying to create a query on a closed store.
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: the data table again displays all items of a page.
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
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
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
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 ).
Fixes writes failing with "error code -30786", which may occur in some corner cases on some devices.
Add Query.copy()
and QueryThreadLocal
to obtain a Query
instance to use in different threads. .
Allow using a relation target ID property with a . E.g. query.property(Order_.customerId)
will map results to the ID of the customer of an order.
Add docs on DbFullException
about .
Fix a crash when querying a value-based index (e.g. @Index(type = IndexType.VALUE)
) on Android 32-bit ARM devices.
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.
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.
Query: throw IllegalStateException
when query is closed instead of crashing the virtual machine.
The Data Browser has been renamed to . Deprecated AndroidObjectBrowser
, use Admin
instead. AndroidObjectBrowser
will be removed in a future release.
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.
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
).
Support 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.
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. .
Support annotating a single property with @Unique(onConflict = ConflictStrategy.REPLACE)
to replace an existing Object if a conflict occurs when doing a put.
Package FlatBuffers version into library to avoid conflicts if your code uses FlatBuffers as well.
Kotlin: add Flow extension functions for BoxStore and Query.
Data browser: display query results if a property has a NaN value.
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 .
Resolve rare ClassNotFoundException: kotlin.text.Charsets
when running processor.
Ensure Query setParameters works if running the x86 library on x64 devices (which could happen if ABI filters were set up incorrectly).
Minor improvements to tooling.
Added API.
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()
work with LazyList
).
Print full name and link to element for @Index
and @Id
errors.
Data Browser drawables are no longer packaged in the regular Android library.
Fixes for one-to-many relations, e.g. allow removing both entity classes of a one-to-many relation.
@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
.
The annotation processor is incremental by default.
Support setting an alias after combining conditions using and()
or or()
.
Turn on incremental annotation processing by default.
Add documentation that string property conditions ignore case by default. Point to using case-sensitive conditions for high-performance look-ups, e.g. .
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. .
Add , objectbox-rxjava3
. In addition objectbox-kotlin
adds extension functions to more easily obtain Rx types, e.g. use query.observable()
to get an Observable
.
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.
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
:
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:
Update your app to use Jetpack (AndroidX); follow the instructions in .
Class transformation works correctly if absolute path contains special characters.
Fixed removing a relation and the related entity class.
Resolved issue to enable query conditions on the target ID property of a ToOne relation.
Box.getAll always returns a mutable list.
Do not overwrite existing objectbox-java or objectbox-kotlin dependency.
Resolved a corner case build time crash when parsing package elements.
When trying to find an appropriate get-method for a property, also check if the return type matches the property type.
Explicitly display an error if two entities with the same name are detected.
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.
Query: improved performance for ordered results with a limit.
Query: throw if a filter is used incorrectly with count or remove.
The release of new made us change name of the JNI library
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.
Unique constraint for properties via
deployed in JCenter
: query for non-null or unique occurrences of entity properties (non-null and unique)
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.
Android: added (architecture components)
: more Kotlin fun with ObjectBox KTX
: helps setting query parameters in complex scenarios (e.g. for properties of linked entities)
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.
Some important limitations apply, see the class documentation for details.
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 ).
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 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.)
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)
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.
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.
We recommend to rename properties and even entities instead.
Creating to-one and to-many relations between objects is possible as well, see the documentation for details.
Once your entity schema is in place, you can .
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.
This is not recommended. Check if using a is a viable alternative.
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.
For details see the documentation about .
Check on Github for status.
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 .
Property conditions: and
Relation conditions:
Order directives can also be chained. Check the method documentation () for details.
are first created (and not yet executed) by calling build()
on the QueryBuilder
.
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.
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.
Property queries ( and ) also offer aggregate functions to directly calculate the minimum, maximum, average, sum and count of all found values:
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.
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:
Only Java/Kotlin. For Dart, use the built-in method.
Now filter those candidates using the Java interface to identify final results
Release notes for ObjectBox 1.5.0 and older.
Full support for Android local tests: use full ObjectBox features in local tests
New count method optimized for a given maximum count
Gradle option to define the package for MyObjectBox explicitly
Query condition startsWith now uses index if available for better performance
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
, 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
Supply an initial database file using BoxStoreBuilder
Gradle plugin detects plain Java projects and configures dependencies
Improved Box.removeAll() performance for entities that have indexes or relations
Fixed converting from arrays in entities
Fixed @NameInDb
Fixed Gradle “androidTestCompile is obsolete” warning
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.
Fixed BoxStore.close being stuck in rare scenarios
Fixed an issue with char properties in entities
Note: This release requires the Android Gradle Plugin 3.0.0 or higher.
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)
Added DbExceptionListener as a central place to listen for DB related exceptions
Minor improvements for ToMany and generated Cursor classes
ToMany: fixed handling of duplicate entries (e.g. fixes swap and reverse operations)
ToMany: fixed removal of non-persisted element for standalone relations
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
ToOne now implements equals() and hashCode() based on the targetId property
Android ABI x86_64 was added to the aar
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
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.
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
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
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
Improved debug logging for transactions and queries: enable this using BoxStoreBuilder.debugFlags(…) with values from the DebugFlags 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
Fix for ObjectBoxLiveData firing twice
Compatibility note: We removed some Box.find methods, which were all tagged as @Temporary. Only the Property based ones remain (for now, also @Temporary).
ObjectBoxLiveData: Implements LiveData from Android Architecture Components
Object ID based methods for ToMany: 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)
UI improvements for ObjectBox browser
Other minor improvements
Fixed query order by float and double
Fixed an missing import if to-many relations referenced a entity in another package
Other minor fixes
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
Improved error messages on build errors
Internal clean up, dropping legacy plugin
Annotation processor detects boolean getters starting with “is”
Fixed a NPE with eager and findFirst() when there is no result
First bug fix release for ObjectBox 1.0.
ToMany allows setting a Comparator to order the List (experimental)
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)
ObjectBox is out of beta! See our announcement blog post for details.
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
Fixed two corner cases with queries
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
For upgrade notes, please check the announcement post.
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!?)
Improved error reporting
Fixed the issue causing a “Illegal state: Tx destroyed/inactive, writeable cursor still available” error log
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))
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 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
Fixed a leak with potentially occurring with indexes
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.
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)
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
@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.)
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
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
Query.forEach() to iterate efficiently over query result objects
Various bug fixes
Data observers with reactive extensions for transformations, thread scheduling, etc.
Optional RxJava 2 library
OR conditions for QueryBuilder allow more powerful queries
Fixed: Changing the order of an entity’s properties could cause errors in some cases
Fixed: Querying using relation IDs
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
put() now uses entity fields directly unless they are private (can be more efficient than calling getters)
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.
Solutions for common issues with ObjectBox for Java and Dart
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.
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 meta model docs on how to resolve the conflicts.
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 meta model docs on why this can happen and how to resolve such conflicts.
See below.
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
DB's last relation 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 meta model docs on why this can happen and how to resolve such conflicts.
This is thrown when applying a transaction (e.g. putting an object) would exceed the maxSizeInKByte (Java)/maxDBSizeInKB (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:
This can have various reasons. In general check your ABI filter setup or add one 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 App Bundle, split APKs and Multidex for workarounds.
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
http://g.co/androidstudio/app-test-app-conflict
for details.
You can easily resolve the version conflict by adding this Gradle dependency: androidTestCompile 'com.google.code.findbugs:jsr305:3.0.2'
Check the data model migration guide 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
Only 64-bit iOS devices are supported. To resolve the build error, configure Architectures in your Xcode project like described in Getting Started for Flutter.
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:
Error code
Errno
Description
1
EPERM
Operation not permitted
2
ENOENT
No such file or directory
3
ESRCH
No such process
4
EINTR
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
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
objectbox-admin.sh
Shell Front-End for ObjectBox Admin Docker Image