Ultra-fast SQL-like queries on Java collections

Overview

Build Status Maven Central

CQEngine - Collection Query Engine

CQEngine – Collection Query Engine – is a high-performance Java collection which can be searched with SQL-like queries, with extremely low latency.

  • Achieve millions of queries per second, with query latencies measured in microseconds
  • Offload query traffic from databases - scale your application tier
  • Outperform databases by a factor of thousands, even on low-end hardware

Supports on-heap persistence, off-heap persistence, disk persistence, and supports MVCC transaction isolation.

Interesting reviews of CQEngine:

The Limits of Iteration

The classic way to retrieve objects matching some criteria from a collection, is to iterate through the collection and apply some tests to each object. If the object matches the criteria, then it is added to a result set. This is repeated for every object in the collection.

Conventional iteration is hugely inefficient, with time complexity O(n t). It can be optimized, but requires statistical knowledge of the makeup of the collection. Read more: The Limits of Iteration

Benchmark Sneak Peek

Even with optimizations applied to convention iteration, CQEngine can outperform conventional iteration by wide margins. Here is a graph for a test comparing CQEngine latency with iteration for a range-type query:

quantized-navigable-index-carid-between.png

  • 1,116,071 queries per second (on a single 1.8GHz CPU core)
  • 0.896 microseconds per query
  • CQEngine is 330187.50% faster than naive iteration
  • CQEngine is 325727.79% faster than optimized iteration

See the Benchmark wiki page for details of this test, and other tests with various types of query.


CQEngine Overview

CQEngine solves the scalability and latency problems of iteration by making it possible to build indexes on the fields of the objects stored in a collection, and applying algorithms based on the rules of set theory to reduce the time complexity of accessing them.

Indexing and Query Plan Optimization

  • Simple Indexes can be added to any number of individual fields in a collection of objects, allowing queries on just those fields to be answered in O(1) time complexity
  • Multiple indexes on the same field can be added, each optimized for different types of query - for example equality, numerical range, string starts with etc.
  • Compound Indexes can be added which span multiple fields, allowing queries referencing several fields to also be answered in O(1) time complexity
  • Nested Queries are fully supported, such as the SQL equivalent of "WHERE color = 'blue' AND(NOT(doors = 2 OR price > 53.00))"
  • Standing Query Indexes can be added; these allow arbitrarily complex queries, or nested query fragments, to be answered in O(1) time complexity, regardless of the number of fields referenced. Large queries containing branches or query fragments for which standing query indexes exist, will automatically benefit from O(1) time complexity evaluation of their branches; in total several indexes might be used to accelerate complex queries
  • Statistical Query Plan Optimization - when several fields have suitable indexes, CQEngine will use statistical information from the indexes, to internally make a query plan which selects the indexes which can perform the query with minimum time complexity. When some referenced fields have suitable indexes and some do not, CQEngine will use the available indexes first, and will then iterate the smallest possible set of results from those indexes to filter objects for the rest of the query. In those cases time complexity will be greater than O(1), but usually significantly less than O(n)
  • Iteration fallback - if no suitable indexes are available, CQEngine will evaluate the query via iteration, using lazy evaluation. CQEngine can always evaluate every query, even if no suitable indexes are available. Queries are not coupled with indexes, so indexes can be added after the fact, to speed up existing queries
  • CQEngine supports full concurrency and expects that objects will be added to and removed from the collection at runtime; CQEngine will take care of updating all registered indexes in realtime
  • Type-safe - nearly all errors in queries result in compile-time errors instead of exceptions at runtime: all indexes, and all queries, are strongly typed using generics at both object-level and field-level
  • On-heap/off-heap/disk - objects can be stored on-heap (like a conventional Java collection), or off-heap (in native memory, within the JVM process but outside the Java heap), or persisted to disk

Several implementations of CQEngine's IndexedCollection are provided, supporting various concurrency and transaction isolation levels:

For more details see TransactionIsolation.


Complete Example

In CQEngine applications mostly interact with IndexedCollection, which is an implementation of java.util.Set, and it provides two additional methods:

Here is a complete example of how to build a collection, add indexes and perform queries. It does not discuss attributes, which are discussed below.

STEP 1: Create a new indexed collection

IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>();

STEP 2: Add some indexes to the collection

cars.addIndex(NavigableIndex.onAttribute(Car.CAR_ID));
cars.addIndex(ReversedRadixTreeIndex.onAttribute(Car.NAME));
cars.addIndex(SuffixTreeIndex.onAttribute(Car.DESCRIPTION));
cars.addIndex(HashIndex.onAttribute(Car.FEATURES));

STEP 3: Add some objects to the collection

cars.add(new Car(1, "ford focus", "great condition, low mileage", Arrays.asList("spare tyre", "sunroof")));
cars.add(new Car(2, "ford taurus", "dirty and unreliable, flat tyre", Arrays.asList("spare tyre", "radio")));
cars.add(new Car(3, "honda civic", "has a flat tyre and high mileage", Arrays.asList("radio")));

STEP 4: Run some queries

Note: add import statement to your class: import static com.googlecode.cqengine.query.QueryFactory.*

  • Example 1: Find cars whose name ends with 'vic' or whose id is less than 2

    Query:

      Query<Car> query1 = or(endsWith(Car.NAME, "vic"), lessThan(Car.CAR_ID, 2));
      cars.retrieve(query1).forEach(System.out::println);

    Prints:

      Car{carId=3, name='honda civic', description='has a flat tyre and high mileage', features=[radio]}
      Car{carId=1, name='ford focus', description='great condition, low mileage', features=[spare tyre, sunroof]}
    
  • Example 2: Find cars whose flat tyre can be replaced

    Query:

      Query<Car> query2 = and(contains(Car.DESCRIPTION, "flat tyre"), equal(Car.FEATURES, "spare tyre"));
      cars.retrieve(query2).forEach(System.out::println);

    Prints:

      Car{carId=2, name='ford taurus', description='dirty and unreliable, flat tyre', features=[spare tyre, radio]}
    
  • Example 3: Find cars which have a sunroof or a radio but are not dirty

    Query:

      Query<Car> query3 = and(in(Car.FEATURES, "sunroof", "radio"), not(contains(Car.DESCRIPTION, "dirty")));
      cars.retrieve(query3).forEach(System.out::println);

    Prints:

      Car{carId=1, name='ford focus', description='great condition, low mileage', features=[spare tyre, sunroof]}
      Car{carId=3, name='honda civic', description='has a flat tyre and high mileage', features=[radio]}
    

Complete source code for these examples can be found here.


String-based queries: SQL and CQN dialects

As an alternative to programmatic queries, CQEngine also has support for running string-based queries on the collection, in either SQL or CQN (CQEngine Native) format.

Example of running an SQL query on a collection (full source here):

public static void main(String[] args) {
    SQLParser<Car> parser = SQLParser.forPojoWithAttributes(Car.class, createAttributes(Car.class));
    IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>();
    cars.addAll(CarFactory.createCollectionOfCars(10));

    ResultSet<Car> results = parser.retrieve(cars, "SELECT * FROM cars WHERE (" +
                                    "(manufacturer = 'Ford' OR manufacturer = 'Honda') " +
                                    "AND price <= 5000.0 " +
                                    "AND color NOT IN ('GREEN', 'WHITE')) " +
                                    "ORDER BY manufacturer DESC, price ASC");
                                    
    results.forEach(System.out::println); // Prints: Honda Accord, Ford Fusion, Ford Focus
}

Example of running a CQN query on a collection (full source here):

public static void main(String[] args) {
    CQNParser<Car> parser = CQNParser.forPojoWithAttributes(Car.class, createAttributes(Car.class));
    IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>();
    cars.addAll(CarFactory.createCollectionOfCars(10));

    ResultSet<Car> results = parser.retrieve(cars,
                                    "and(" +
                                        "or(equal(\"manufacturer\", \"Ford\"), equal(\"manufacturer\", \"Honda\")), " +
                                        "lessThanOrEqualTo(\"price\", 5000.0), " +
                                        "not(in(\"color\", GREEN, WHITE))" +
                                    ")");
                                    
    results.forEach(System.out::println); // Prints: Ford Focus, Ford Fusion, Honda Accord
}

Feature Matrix for Included Indexes

Legend for the feature matrix

Abbreviation Meaning Example
EQ Equality equal(Car.DOORS, 4)
IN Equality, multiple values in(Car.DOORS, 3, 4, 5)
LT Less Than (numerical range / Comparable) lessThan(Car.PRICE, 5000.0)
GT Greater Than (numerical range / Comparable) greaterThan(Car.PRICE, 2000.0)
BT Between (numerical range / Comparable) between(Car.PRICE, 2000.0, 5000.0)
SW String Starts With startsWith(Car.NAME, "For")
EW String Ends With endsWith(Car.NAME, "ord")
SC String Contains contains(Car.NAME, "or")
CI String Is Contained In isContainedIn(Car.NAME, "I am shopping for a Ford Focus car")
RX String Matches Regular Expression matchesRegex(Car.MODEL, "Ford.*")
HS Has (aka IS NOT NULL) has(Car.DESCRIPTION) / not(has(Car.DESCRIPTION))
SQ Standing Query Can the index accelerate a query (as opposed to an attribute) to provide constant time complexity for any simple query, complex query, or fragment
QZ Quantization Does the index accept a quantizer to control granularity
LP LongestPrefix longestPrefix(Car.NAME, "Ford")

Note: CQEngine also supports complex queries via and, or, not, and combinations thereof, across all indexes.

Index Feature Matrix

Index Type EQ IN LT GT BT SW EW SC CI HS RX SQ QZ LP
Hash
Unique
Compound
Navigable
PartialNavigable
RadixTree
ReversedRadixTree
InvertedRadixTree
SuffixTree
StandingQuery
Fallback
OffHeap [1]
PartialOffHeap
Disk [1]
PartialDisk
[1] See: forStandingQuery()

The Benchmark page contains examples of how to add these indexes to a collection, and measures their impact on latency.


Attributes

Read Fields

CQEngine needs to access fields inside objects, so that it can build indexes on fields, and retrieve the value of a certain field from any given object.

CQEngine does not use reflection to do this; instead it uses attributes, which is a more powerful concept. An attribute is an accessor object which can read the value of a certain field in a POJO.

Here's how to define an attribute for a Car object (a POJO), which reads the Car.carId field:

public static final Attribute<Car, Integer> CAR_ID = new SimpleAttribute<Car, Integer>("carId") {
    public Integer getValue(Car car, QueryOptions queryOptions) { return car.carId; }
};

...or alternatively, from a lambda expression or method reference:

public static final Attribute<Car, Integer> Car_ID = attribute("carId", Car::getCarId);

(For some caveats on using lambdas, please read LambdaAttributes)

Usually attributes are defined as anonymous static final objects like this. Supplying the "carId" string parameter to the constructor is actually optional, but it is recommended as it will appear in query toStrings.

Since this attribute reads a field from a Car object, the usual place to put the attribute is inside the Car class - and this makes queries more readable. However it could really be defined in any class, such as in a CarAttributes class or similar. The example above is for a SimpleAttribute, which is designed for fields containing only one value.

CQEngine also supports MultiValueAttribute which can read the values of fields which themselves are collections. And so it supports building indexes on objects based on things like keywords associated with those objects.

Here's how to define a MultiValueAttribute for a Car object which reads the values from Car.features where that field is a List<String>:

public static final Attribute<Car, String> FEATURES = new MultiValueAttribute<Car, String>("features") {
    public Iterable<String> getValues(Car car, QueryOptions queryOptions) { return car.features; }
};

...or alternatively, from a lambda expression or method reference:

public static final Attribute<Car, String> FEATURES = attribute(String.class, "features", Car::getFeatures);

Null values

Note if your data contains null values, you should use SimpleNullableAttribute or MultiValueNullableAttribute instead.

In particular, note that SimpleAttribute and MultiValueAttribute do not perform any null checking on your data, and so if your data inadvertently contains null values, you may get obscure NullPointerExceptions. This is because null checking does not come for free. Attributes are accessed heavily, and the non-nullable versions of these attributes are designed to minimize latency by skipping explicit null checks. They defer to the JVM to do the null checking implicitly.

As a rule of thumb, if you get a NullPointerException, it's probably because you used the wrong type of attribute. The problem will usually go away if you switch your code to use a nullable attribute instead. If you don't know if your data may contain null values, just use the nullable attributes. They contain the logic to check for and handle null values automatically.

The nullable attributes also allow CQEngine to work with object inheritance, where some objects in the collection have certain optional fields (e.g. in subclasses) while others might not.

Creating queries dynamically

Dynamic queries can be composed at runtime by instantiating and combining Query objects directly; see this package and this package. For advanced cases, it is also possible to define attributes at runtime, using ReflectiveAttribute or AttributeBytecodeGenerator.

Generate attributes automatically

CQEngine also provides several ways to generate attributes automatically.

Note these are an alternative to using ReflectiveAttribute, which was discussed above. Whereas ReflectiveAttribute is a special type of attribute which reads values at runtime using reflection, AttributeSourceGenerator and AttributeBytecodeGenerator generate code for attributes which is compiled and so does not use reflection at runtime, which can be more efficient.

  • AttributeSourceGenerator can automatically generate the source code for the simple and multi-value attributes discussed above.
  • AttributeBytecodeGenerator can automatically generate the class bytecode for the simple and multi-value attributes discussed above, and load them into the application at runtime as if they had been compiled from source code.

See AutoGenerateAttributes for more details.

Attributes as Functions

It can be noted that attributes are only required to return a value given an object. Although most will do so, there is no requirement that an attribute must provide a value by reading a field in the object. As such attributes can be virtual, implemented as functions.

Calculated Attributes

An attribute can calculate an appropriate value for an object, based on a function applied to data contained in other fields or from external data sources.

Here's how to define a calculated (or virtual) attribute by applying a function over the Car's other fields:

public static final Attribute<Car, Boolean> IS_DIRTY = new SimpleAttribute<Car, Boolean>("is_dirty") {
    public Boolean getValue(Car car, QueryOptions queryOptions) { return car.description.contains("dirty"); }
};

...or, the same thing using a lambda:

public static final Attribute<Car, Boolean> IS_DIRTY = attribute("is_dirty", car -> car.description.contains("dirty"));

A HashIndex could be built on the virtual attribute above, enabling fast retrievals of cars which are either dirty or not dirty, without needing to scan the collection.

Associations with other IndexedCollections or External Data Sources

Here is an example for a virtual attribute which associates with each Car a list of locations which can service it, from an external data source:

public static final Attribute<Car, String> SERVICE_LOCATIONS = new MultiValueAttribute<Car, String>() {
    public List<String> getValues(Car car, QueryOptions queryOptions) {
        return CarServiceManager.getServiceLocationsForCar(car);
    }
};

The attribute above would allow the IndexedCollection of cars to be searched for cars which have servicing options in a particular location.

The locations which service a car, could alternatively be retrieved from another IndexedCollection, of Garages, for example. Care should be taken if building indexes on virtual attributes however, if referenced data might change leaving obsolete information in indexes. A strategy to accommodate this is: if no index exists for a virtual attribute referenced in a query, and other attributes are also referenced in the query for which indexes exist, CQEngine will automatically reduce the candidate set of objects to the minimum using other indexes before querying the virtual attribute. In turn if virtual attributes perform retrievals from other IndexedCollections, then those collections could be indexed appropriately without a risk of stale data.


Joins

The examples above define attributes on a primary IndexedCollection which read data from secondary collections or external data sources.

It is also possible to perform SQL EXISTS-type queries and JOINs between IndexedCollections on the query side (as opposed to on the attribute side). See Joins for examples.


Persistence on-heap, off-heap, disk

CQEngine's IndexedCollections can be configured to store objects added to them on-heap (the default), or off-heap, or on disk.

On-heap

Store the collection on the Java heap:

IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>();

Off-heap

Store the collection in native memory, within the JVM process but outside the Java heap:

IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>(OffHeapPersistence.onPrimaryKey(Car.CAR_ID));

Note that the off-heap persistence will automatically create an index on the specified primary key attribute, so there is no need to add an index on that attribute later.

Disk

Store the collection in a temp file on disk (then see DiskPersistence.getFile()):

IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>(DiskPersistence.onPrimaryKey(Car.CAR_ID));

Or, store the collection in a particular file on disk:

IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>(DiskPersistence.onPrimaryKeyInFile(Car.CAR_ID, new File("cars.dat")));

Note that the disk persistence will automatically create an index on the specified primary key attribute, so there is no need to add an index on that attribute later.

Wrapping

Wrap any Java collection, in a CQEngine IndexedCollection without any copying of objects.

  • This can be a convenient way to run queries or build indexes on existing collections.
  • However some caveats relating to concurrency support and the performance of the underlying collection apply, see WrappingPersistence for details.
Collection<Car> collection = // obtain any Java collection

IndexedCollection<Car> indexedCollection = new ConcurrentIndexedCollection<Car>(
        WrappingPersistence.aroundCollection(collection)
);

Composite

CompositePersistence configures a combination of persistence types for use within the same collection. The collection itself will be persisted in the first persistence provided (the primary persistence), and the additional persistences provided will be used by off-heap or disk indexes added to the collection subsequently.

Store the collection on-heap, and also configure DiskPersistence for use by DiskIndexes added to the collection subsequently:

IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>(CompositePersistence.of(
    OnHeapPersistence.onPrimaryKey(Car.CAR_ID),
    DiskPersistence.onPrimaryKeyInFile(Car.CAR_ID, new File("cars.dat"))
));

Index persistence

Indexes can similarly be stored on-heap, off-heap, or on disk. Each index requires a certain type of persistence. It is necessary to configure the collection in advance with an appropriate combination of persistences for use by whichever indexes are added.

It is possible to store the collection on-heap, but to store some indexes off-heap. Similarly it is possible to have a variety of index types on the same collection, each using a different type of persistence. On-heap persistence is by far the fastest, followed by off-heap persistence, and then by disk persistence.

If both the collection and all of its indexes are stored off-heap or on disk, then it is possible to have extremely large collections which don't use any heap memory or RAM at all.

CQEngine has been tested using off-heap persistence with collections of 10 million objects, and using disk persistence with collections of 100 million objects.

On-heap

Add an on-heap index on "manufacturer":

cars.addIndex(NavigableIndex.onAttribute(Car.MANUFACTURER));

Off-heap

Add an off-heap index on "manufacturer":

cars.addIndex(OffHeapIndex.onAttribute(Car.MANUFACTURER));

Disk

Add a disk index on "manufacturer":

cars.addIndex(DiskIndex.onAttribute(Car.MANUFACTURER));

Querying with persistence

When either the IndexedCollection, or one or more indexes are located off-heap or on disk, take care to close the ResultSet when finished reading. You can use a try-with-resources block to achieve this:

try (ResultSet<Car> results = cars.retrieve(equal(Car.MANUFACTURER, "Ford"))) {
    results.forEach(System.out::println);
}

Result Sets

CQEngine ResultSets provide the following methods:

  • iterator() - Allows the ResultSet to be iterated, returning the next object matching the query in each iteration as determined via lazy evaluation

    • Result sets support concurrent iteration while the collection is being modified; the set of objects returned simply may or may not reflect changes made during iteration (depending on whether changes are made to areas of the collection or indexes already iterated or not)
  • uniqueResult() - Useful if the query is expected to only match one object, this method returns the first object which would be returned by the iterator, and it throws an exception if zero or more than one object is found

  • size() - Returns the number of objects which would be returned by the ResultSet if it was iterated; CQEngine can often accelerate this calculation of size, based on the sizes of individual sets in indexes; see JavaDoc for details

  • contains() - Tests if a given object would be contained in results matching a query; this is also an accelerated operation; when suitable indexes are available, CQEngine can avoid iterating results to test for containment; see JavaDoc for details

  • getRetrievalCost() - This is a metric used internally by CQEngine to allow it to choose between multiple indexes which support the query. This could occasionally be used by applications to ascertain if suitable indexes are available for any particular query, this will be Integer.MAX_VALUE for queries for which no suitable indexes are available

  • getMergeCost() - This is a metric used internally by CQEngine to allow it to re-order elements of the query to minimize time complexity; for example CQEngine will order intersections such that the smallest set drives the merge; this metric is roughly based on the theoretical cost to iterate underlying result sets

    • For query fragments requiring set union (or-based queries), this will be the sum of merge costs from underlying result sets
    • For query fragments requiring set intersection (and-based queries), this will be the Math.min() of merge costs from underlying result sets, because intersections will be re-ordered to perform lowest-merge-cost intersections first
    • For query fragments requiring set difference (not-based queries), this will be the merge cost from the first underlying result set
  • stream() - Returns a Java 8+ Stream allowing CQEngine results to be grouped, aggregated, and transformed in flexible ways using lambda expressions.

  • close() - Releases any resources or closes the transaction which was opened for the query. Whether or not it is necessary to close the ResultSet depends on which implementation of IndexedCollection is in use and the types of indexes added to it.


Deduplicating Results

It is possible that a query would result in the same object being returned more than once.

For example if an object matches several attribute values specified in an or-type query, then the object will be returned multiple times, one time for each attribute matched. Intersections (and-type queries) and negations (not-type queries) do not produce duplicates.

By default, CQEngine does not perform de-duplication of results; however it can be instructed to do so, using various strategies such as Logical Elimination and Materialize. Read more: DeduplicationStrategies


Ordering Results

By default, CQEngine does not order results; it simply returns objects in the order it finds them in the collection or in indexes.

CQEngine can be instructed to order results via query options as follows.

Order by price descending

ResultSet<Car> results = cars.retrieve(query, queryOptions(orderBy(descending(Car.PRICE))));

Order by price descending, then number of doors ascending

ResultSet<Car> results = cars.retrieve(query, queryOptions(orderBy(descending(Car.PRICE), ascending(Car.DOORS))));

Note that ordering results as above uses the default materialize ordering strategy. This is relatively expensive, dependent on the number of objects matching the query, and can cause latency in accessing the first object. It requires all results to be materialized into a sorted set up-front before iteration can begin.

Index-accelerated ordering

CQEngine also has support to use an index to accelerate, or eliminate, the overhead of ordering results. This strategy reduces the latency to access the first object in the sorted results, at the expense of adding more total overhead if the entire ResultSet was iterated. Read more: OrderingStrategies


Merge Strategies

Merge strategies are the algorithms CQEngine uses to evaluate queries which have multiple branches.

By default CQEngine will use strategies which should suit most applications, however these strategies can be overridden to tune performance. Read more: MergeStrategies


Index Quantization, Granularity, and tuning index size

Quantization involves converting fine-grained or continuous values, to discrete or coarse-grained values. A Quantizer is a function which takes fine-grained values as input, and maps those values to coarse-grained counterparts as its output, by discarding some precision.

Quantization can be a useful tool to tune the size of indexes, trading a reduction in index size, for increases in CPU overhead and vice-versa. Read more: Quantization and included Quantizers


Grouping and Aggregation (GROUP BY, SUM...)

CQEngine has been designed with support for grouping and aggregation in mind, but note that this is not built into the CQEngine library itself, because CQEngine is designed to integrate with Java 8+ Streams. This allows CQEngine results to be grouped, aggregated, and transformed in flexible ways using lambda expressions.

CQEngine ResultSet can be converted into a Java 8 Stream by calling ResultSet.stream().

Note that Streams are evaluated via filtering and they do not avail of CQEngine indexes. So for best performance, as much of the overall query as possible should be encapsulated in the CQEngine query, as opposed to in lambda expressions in the stream. This combination would dramatically outperform a stream and lambda expression alone, which simply filtered the collection.

Here's how to transform a ResultSet into a Stream, to compute the distinct set of Colors of cars which match a CQEngine query.

public static void main(String[] args) {
    IndexedCollection<Car> cars = new ConcurrentIndexedCollection<>();
    cars.addAll(CarFactory.createCollectionOfCars(10));
    cars.addIndex(NavigableIndex.onAttribute(Car.MANUFACTURER));

    Set<Car.Color> distinctColorsOfFordCars = cars.retrieve(equal(Car.MANUFACTURER, "Ford"))
            .stream()
            .map(Car::getColor)
            .collect(Collectors.toSet());

    System.out.println(distinctColorsOfFordCars); // prints: [GREEN, RED]
}

Accessing Index Metadata and Statistics from MetadataEngine

The MetadataEngine, is a high-level API which can retrieve metatadata and statistics from indexes which have been added to the collection.

It provides access to the following:

  • Frequency distributions (the counts of each attribute value stored in an index)
  • Distinct keys (the distinct attribute values in an index, optionally within a range between x and y)
  • Streams of attribute values and associated objects stored in an index (ascending/descending order, optionally within a range between x and y)
  • Count of distinct keys (how many distinct attribute values are in an index)
  • Count for a specific key (how many objects match a specific attribute value)

For more information, see JavaDocs for: MetadataEngine, AttributeMetadata, SortedAttributeMetadata


Using CQEngine with Hibernate / JPA / ORM Frameworks

CQEngine has seamless integration with JPA/ORM frameworks such as Hibernate or EclipseLink.

Simply put, CQEngine can build indexes on, and query, any type of Java collection or arbitrary data source. ORM frameworks return entity objects loaded from database tables in Java collections, therefore CQEngine can act as a very fast in-memory query engine on top of such data.


Usage in Maven and Non-Maven Projects

CQEngine is in Maven Central, and can be added to a Maven project as follows:

<dependency>
    <groupId>com.googlecode.cqengine</groupId>
    <artifactId>cqengine</artifactId>
    <version>x.x.x</version>
</dependency>

See ReleaseNotes for the latest version number.

For non-Maven projects, a version built with maven-shade-plugin is also provided, which contains CQEngine and all of its own dependencies packaged in a single jar file (ending "-all"). It can be downloaded from Maven central as "-all.jar" here.


Using CQEngine in Scala, Kotlin, or other JVM languages

CQEngine should generally be compatible with other JVM languages besides Java too, however it can be necessary to apply a few tricks to make it work. See OtherJVMLanguages.md for some tips.


Related Projects

  • CQEngine is somewhat similar to Microsoft LINQ, but a difference is LINQ queries on collections are evaluated via iteration/filtering whereas CQEngine uses set theory, thus CQEngine would outperform LINQ

  • Concurrent Trees provides Concurrent Radix Trees and Concurrent Suffix Trees, used by some indexes in CQEngine


Project Status

  • CQEngine 3.6.0 is the current release as of writing (January 2021), and is in Maven central
  • A ReleaseNotes page has been added to document changes between releases
  • API / JavaDocs are available here

Report any bugs/feature requests in the Issues tab. For support please use the Discussion Forum, not direct email to the developers.

Many thanks to JetBrains for supporting CQEngine with free IntelliJ licenses!

Comments
  • Support for Map as collection object?

    Support for Map as collection object?

    Any plans to include for support Map as the collection object or would you accept a pull request to add it?

    I can currently do something like this:

    new SimpleAttribute<Map, String>(attributeName) {
                @Override
                public String getValue(Map o, QueryOptions queryOptions) {
                    return (String) o.get(attributeName);
                }
            };
    

    But would good to have something more builtin, eg AttributeFromMap

    Cheers, Chris

    enhancement 
    opened by kimptoc 17
  • Performance is not looking correct

    Performance is not looking correct

    I have this code

    public class DemoApplication {

    public static void main(String[] args) throws InterruptedException {
    
        Properties pr = new Properties();
        pr.put("shared_cache",true);
        IndexedCollection<Car> cars = new ConcurrentIndexedCollection<Car>(DiskPersistence.onPrimaryKeyInFileWithProperties(Car.CAR_ID, new File("d:/cars.dat"), pr));
        cars.addIndex(NavigableIndex.onAttribute(Car.MODEL));
    
        log.info("Creating collection.");
        Set<Car> collectionOfCars = CarFactory.createCollectionOfCars(2_000_000);
        cars.addAll(collectionOfCars);
        log.info("Collection created");
    
        while (true) {
            log.info("-----------------------------------");
            log.info("Retrieving ...");
            List<Car> fusion = cars.retrieve(equal(Car.MODEL, "Fusion")).stream().collect(Collectors.toList());
            System.out.println(fusion.size());
            log.info("Retrieved");
        }
    
    
    }
    

    }

    I am adding around 2 Million Cars. But when I monitor the the memory itsconstantly very high though it should not be since I am using Disk persistence

    image

    opened by muhdkhokhar 14
  • Type Index

    Type Index

    Is any plans to introduce some kind of TypeIndex? To retrieve subclasses of holding elements, for example when Collection<? extends T>. In that case I want to have indexes of some subclasses of T

    opened by ShishkinDmitriy 13
  • [SQLITE_BUSY]  The database file is locked (database is locked)

    [SQLITE_BUSY] The database file is locked (database is locked)

    Hi Niall

    I am running into this in a multithreaded environment when I add an item to the index.

    ! org.sqlite.SQLiteException: [SQLITE_BUSY]  The database file is locked (database is locked)
    ! at org.sqlite.core.DB.newSQLException(DB.java:909)
    ! at org.sqlite.core.DB.newSQLException(DB.java:921)
    ! at org.sqlite.core.DB.throwex(DB.java:886)
    ! at org.sqlite.core.DB.executeBatch(DB.java:774)
    ! at org.sqlite.core.CorePreparedStatement.executeBatch(CorePreparedStatement.java:84)
    ! at com.googlecode.cqengine.index.sqlite.support.DBQueries.bulkAdd(DBQueries.java:302)
    ! ... 15 common frames omitted
    ! Causing: java.lang.IllegalStateException: Unable to bulk add rows to the index table: Id. Rolled back: true
    ! at com.googlecode.cqengine.index.sqlite.support.DBQueries.bulkAdd(DBQueries.java:320)
    ! at com.googlecode.cqengine.index.sqlite.SQLiteIndex.doAddAll(SQLiteIndex.java:468)
    ! at com.googlecode.cqengine.index.sqlite.SQLiteIndex.addAll(SQLiteIndex.java:425)
    ! at com.googlecode.cqengine.index.sqlite.SQLiteIdentityIndex.addAll(SQLiteIdentityIndex.java:113)
    ! at com.googlecode.cqengine.persistence.support.sqlite.SQLiteObjectStore.add(SQLiteObjectStore.java:106)
    ! at com.googlecode.cqengine.ConcurrentIndexedCollection.add(ConcurrentIndexedCollection.java:350)
    

    Index is setup as follows (in a singleton class)

    private final IndexedCollection<PendingItem> pendingItems  = new ConcurrentIndexedCollection<PendingItem>(DiskPersistence.onPrimaryKeyInFile(PendingItem.ID, persistentIndex)); 
    

    On calling pendingItems.add(item); I run into the DB Locked error.

    Any ideas ? Is add not threadsafe?

    opened by jayaramcs 11
  • And.calcHashCode being performance Bottlneck

    And.calcHashCode being performance Bottlneck

    We make a huge number of queries while processing an http request. These 
    queries are unique in nature, that they do not appear again. Their sub queries 
    may appear again.
    
    Where does the calcHashCode() of the main AND query being used?
    
    query = and(q1, q2, q3, ... );
    
    We do roughly 50-500 of these for every request, and And.calcHashCode() takes 
    up majority of our server time.
    
    Do you have any recommendations regarding minimizing this overhead?
    

    Original issue reported on code.google.com by [email protected] on 28 Oct 2013 at 4:06

    opened by GoogleCodeExporter 11
  • Null pointer exception indexed attribute has null value.

    Null pointer exception indexed attribute has null value.

    What steps will reproduce the problem?
    1.Create IndexedCollection for a POJO type and add index on any attribute.
    2.Add a object of that POJO with indexed attribute null.
    3. Works with empty string.
    
    What is the expected output? What do you see instead?
    Should behave as with empty string even with complex type is null there.
    Otherwise user have to check and set empty string for null.
    
    What version of the product are you using? On what operating system?
    0.9.1-all
    
    Please provide any additional information below.
    
    
    

    Original issue reported on code.google.com by [email protected] on 25 Oct 2012 at 2:47

    opened by GoogleCodeExporter 11
  • Disk removal issue (TEST Case attached) ****URGENT*****

    Disk removal issue (TEST Case attached) ****URGENT*****

    Hi

    Please have a look at example

    See the remove is not working

    I have attached all the classes please run the diskissue class.

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.math.BigDecimal;
    
    /**
     * Created by muhdk on 20/11/2019.
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class Person {
    
        private String name;
        private BigDecimal age;
    }
    
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    
    /**
     * Created by muhdk on 20/11/2019.
     */
    
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class DiskIssueClass {
    
        Long id;
        private List any;
    
    }
    
    
    
    import com.googlecode.cqengine.ConcurrentIndexedCollection;
    import com.googlecode.cqengine.attribute.SimpleAttribute;
    import com.googlecode.cqengine.persistence.disk.DiskPersistence;
    import com.googlecode.cqengine.query.option.QueryOptions;
    
    import java.io.File;
    import java.math.BigDecimal;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Properties;
    
    /**
     * Created by muhdk on 20/11/2019.
     */
    public class DiskBigDecimalIssue {
    
        public static void main(String[] args) {
    
            File dbFile = new File("D:/test.db");
            if (dbFile.exists())
                dbFile.delete();
            SimpleAttribute<DiskIssueClass, Long> simpleAttribute = new SimpleAttribute<DiskIssueClass, Long>() {
                @Override
                public Long getValue(DiskIssueClass object, QueryOptions queryOptions) {
                    return object.getId();
                }
            };
    
            DiskPersistence<DiskIssueClass, Long> diskIssueClassIntegerDiskPersistence = DiskPersistence
                    .onPrimaryKeyInFileWithProperties(simpleAttribute, dbFile, new Properties());
    
            ConcurrentIndexedCollection<DiskIssueClass> diskCollection = new ConcurrentIndexedCollection<>(diskIssueClassIntegerDiskPersistence);
    
            List<Person> personList = new ArrayList<>();
    
            personList.add(Person.builder().name("test").age(new BigDecimal("0")).build());
            personList.add(Person.builder().name("joh").age(new BigDecimal("0")).build());
            boolean add = diskCollection.add(DiskIssueClass.builder().id(1l).any(personList).build());
    
            System.out.println(add);
    
            DiskIssueClass diskIssueClass = diskCollection.stream().findAny().get();
    
            boolean remove = diskCollection.remove(diskIssueClass);
    
            System.out.println(remove);
    
    
        }
    }
    
    opened by muhdkhokhar 10
  • 2.9.1 handicapped subclassing Equal

    2.9.1 handicapped subclassing Equal

    When I upgrading to 2.9.1 (2.9.0 went through fine), I experienced a new test failure.

    I am using a subclassed implementation of Equal to provide special handling of certain cases of equality (particularly, making sure byte[] equality comparison is done right [as opposed to the solution suggested in #75, requiring wrapping byte arrays]). It worked fine until I attempted to update cqengine to 2.9.1.

    I am not sure why this behaviour happened now (perhaps some of the optimizations affected the internal workflow — yet to figure out the exact reason), but I was thinking if it would make sense to replace queryClass.equals(Equal.class) and similar constructs to Equal.class.isAssignableFrom(queryClass)? Any objections to this?

    opened by yrashk 10
  • ExistsIn query performance

    ExistsIn query performance

    I have two collections with something like master-detail relation (ForeignKey -> PrimaryKey in database terms). I basically need to perform, in most primitive form, a query like this:

     Query<Item> query = QueryFactory.and(QueryFactory.equal(ID, randomId()), QueryFactory.existsIn(secondCollection, ID, EID));
    Item result = firstCollection.retrieve(query).iterator().next();
    

    Collections are indexed by ID (UniqueIndex - like primary key) and EID (HashIndex - foreign key) respectively. I've put JMH benchmark to strip off my project specifics and compare different options of retrieving results: https://github.com/uujava/cqengine-query-jmh And got the results I could not explain: https://github.com/uujava/cqengine-query-jmh/releases/tag/initial_version

    Comparing a case QueryTest.streamJoin

        @Benchmark
        public void streamJoin(Blackhole bh) {
            Long value = randomId();
            Equal<Long, Long> equal = QueryFactory.equal(EID, value);
            Long result = StreamSupport.stream(second.retrieve(equal).spliterator(), false).map(found -> {
                Equal<Long, Long> subQ = QueryFactory.equal(ID, found);
                return first.retrieve(subQ).iterator().next();
            }).findFirst().get();
            bh.consume(result);
        }
    

    With a case QueryTest.uniqueExistsJoin

        @Benchmark
        public void uniqueExistsJoin(Blackhole bh) {
            Query<Long> query = QueryFactory.and(QueryFactory.equal(ID, randomId()), QueryFactory.existsIn(second, ID, EID));
            bh.consume(first.retrieve(query).iterator().next());
        }
    

    I've got 434088,196 vs 12,434 -> 35000 times difference.

    jmh with -prof stack reveals that the QueryTest.uniqueExistsJoin most time spent in:

     55.2%  55.2% java.util.concurrent.ConcurrentHashMap$Traverser.advance
     44.3%  44.3% java.util.AbstractSet.hashCode
    

    I guess calculating hashCode on a big set of something.

    Not sure if it's an issue, or my misuse of existsIn API.

    opened by uujava 10
  • TransactionalIndexedCollection and UniqueIndex

    TransactionalIndexedCollection and UniqueIndex

    When I try to update an object with a unique index via update on a TransactionalIndexedCollection, I get the error below.

    Code is here: https://github.com/kimptoc/cqengine-test/blob/master/src/main/java/TestUpdateOnTransIndexColl.java

    Exception in thread "Thread-0" com.googlecode.cqengine.index.unique.UniqueIndex$UniqueConstraintViolatedException: The application has attempted to add a duplicate object to the UniqueIndex on attribute 'Id', potentially causing inconsistencies between indexes. UniqueIndex should not be used with attributes which do not uniquely identify objects. Problematic attribute value: 'uniqueId', problematic duplicate object: {Version=2, Size=1, Height=3, Id=uniqueId, Width=4, Distance=2}
        at com.googlecode.cqengine.index.unique.UniqueIndex.addAll(UniqueIndex.java:233)
        at com.googlecode.cqengine.engine.CollectionQueryEngine$12.perform(CollectionQueryEngine.java:1116)
        at com.googlecode.cqengine.engine.CollectionQueryEngine.forEachIndexDo(CollectionQueryEngine.java:1197)
        at com.googlecode.cqengine.engine.CollectionQueryEngine.addAll(CollectionQueryEngine.java:1113)
        at com.googlecode.cqengine.ConcurrentIndexedCollection.doAddAll(ConcurrentIndexedCollection.java:443)
        at com.googlecode.cqengine.TransactionalIndexedCollection.update(TransactionalIndexedCollection.java:221)
        at com.googlecode.cqengine.TransactionalIndexedCollection.update(TransactionalIndexedCollection.java:150)
        at Updater.run(Updater.java:49)
        at java.lang.Thread.run(Thread.java:745)
    

    Is that to be expected?

    Cheers.

    opened by kimptoc 10
  • Offheap Result set closing Issue

    Offheap Result set closing Issue

    Hi I am writting a generic library in my code that returns the ResultSet based on the query.

    The consumer does whatever it does. They may read the stream or use the count.

    The issue is somtimes its putting a lock on the collectino and when getting the collection back it just hangs foreever. After analying the code

    public Connection getConnection(Index<?> index, QueryOptions queryOptions) { // Acquire a read lock IFF the READ_REQUEST flag has been set, otherwise acquire a write lock by default... final Lock connectionLock = isFlagEnabled(queryOptions, READ_REQUEST) ? readWriteLock.readLock() : readWriteLock.writeLock();

        connectionLock.lock();
        Connection connection;
        try {
            connection = getConnectionInternal(index, queryOptions);
        }
        catch (RuntimeException e) {
            connectionLock.unlock();
            throw e;
        }
        return LockReleasingConnection.wrap(connection, connectionLock);
    }
    

    This seems to be culprit.

    I know you mentioned it that the result set should be closed. But I am not sure how the client will close the result set.

    Should I not return the resultset? If not then how can I write generic method like

    ResultSet execute(Query q)

    What are the options

    opened by muhdkhokhar 9
  • LongestPrefixMatch in case of Index Ordering strategy - Draft version(Proposal)

    LongestPrefixMatch in case of Index Ordering strategy - Draft version(Proposal)

    the Use Of Inverted Radix Tree adopted into CqEngine once there are Longest Prefix match attributes provided. what was done on this Solution is to cover the following

    1. get the Inverted Radix Holder that has the ability to hold inverted radix trees for specific key ( Setter and getter added)
    2. the ability from the cqengine to receive the Longest prefix and attributes part of the query options
    3. Logic in case of Longest Prefix match attributes provided and index ordering strategy called , get the Appropriate inverted Radix Tree and on it , get all the prefixes loaded prehand in our case its happening during the start up and then add to the currrent query instead of Longest Prefix match Operator , add the In Operator with prefixes got from the Inverted Radix Tree finally we are invoking the retrieveWithIndexOrderingStrategy and then got the result of the longest prefix columns arraged in descending order. final stage is to filter the result and find if we have 1 record of longest or the length of the first longest reduced .

    we test the soluation from performance wise and we found huge improvement from what currently exist . this is only proposal soluation . please review and let me knows how i can improve it to make it better from all the perspectives.

    opened by fouadazem 0
  • Support for ANTLR 4.10.1

    Support for ANTLR 4.10.1

    I'm upgrading a code base that uses cqengine to Jakarta EE 9.0. It uses Hibernate, and that uses ANTLR 4.10.1. This causes problems for CQEngine like this:

    Caused by: java.lang.UnsupportedOperationException: java.io.InvalidClassException: org.antlr.v4.runtime.atn.ATN; Could not deserialize ATN with version 3 (expected 4).
    	at org.antlr.v4.runtime.atn.ATNDeserializer.deserialize(ATNDeserializer.java:56)
    	at org.antlr.v4.runtime.atn.ATNDeserializer.deserialize(ATNDeserializer.java:48)
    	at com.googlecode.cqengine.query.parser.sql.grammar.SQLGrammarLexer.<clinit>(SQLGrammarLexer.java:809)
    	... 126 more
    Caused by: java.io.InvalidClassException: org.antlr.v4.runtime.atn.ATN; Could not deserialize ATN with version 3 (expected 4).
    	... 129 more
    

    We also get warnings like this:

    ANTLR Tool version 4.7 used for code generation does not match the current runtime version 4.10.1
    ANTLR Runtime version 4.7.2 used for parser compilation does not match the current runtime version 4.10.1
    

    What can we do about this?

    opened by jvissers 5
  • Kryo generates InaccessibleObjectException - Make Dependencies

    Kryo generates InaccessibleObjectException - Make Dependencies "Java 17 compatible"

    I am using my Project barbelhisto-core here in github in a JDK 17 environment. My tests are failing cause some Kryo Serializers use setAccessible by default.

    de.javakaffee.kryoserializers.ArraysAsListSerializer

    This results in an exception:

    Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.lang.Object[] java.util.Arrays$ArrayList.a accessible: module java.base does not "opens java.util" to unnamed module @50b494a6 at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) at java.base/java.lang.reflect.Field.setAccessible(Field.java:172) at de.javakaffee.kryoserializers.ArraysAsListSerializer.(ArraysAsListSerializer.java:45)

    Is there a plan to support Java 17 and above environments?

    opened by nschlimm 0
  • Support for attribute accessors for Java record type

    Support for attribute accessors for Java record type

    Maybe a bit naive, but my initial instinct was to model the values in my lookup set as instances of Java record. However, it seems that attribute generation does not have support for that. Is that something that you want to consider adding for a future version?

    opened by jvissers 1
  • TransactionalIndexedCollection support for UniqueIndex is broken

    TransactionalIndexedCollection support for UniqueIndex is broken

    TransactionalIndexedCollection<Car> cache = new TransactionalIndexedCollection<>(Car.class);
    cache.addIndex(UniqueIndex.onAttribute(Car.CAR_ID));
    cache.add(new Car(1, "Ferrari"));
    
    cache.update(Collections.singletonList(new Car(1, "Ferrari")), Collections.singletonList(new Car(1, "Mercedes")));
    

    I expected this to succeed since I am basically trying to update CAR_ID = 1 with new data.

    However I get - UniqueConstraintViolatedException

    com.googlecode.cqengine.index.unique.UniqueIndex$UniqueConstraintViolatedException: The application has attempted to add a duplicate object to the UniqueIndex on attribute 'carId', potentially causing inconsistencies between indexes. UniqueIndex should not be used with attributes which do not uniquely identify objects. Problematic attribute value: '1', problematic duplicate object: Car[carId=1, name='Mercedes']

    Looking at https://github.com/npgall/cqengine/blob/a0fb1bf6bae602ec9956fde329b71a118844d687/code/src/main/java/com/googlecode/cqengine/TransactionalIndexedCollection.java#L248-L263

    If we move DELETE before ADD, unique index would work.

    I don't fully understand exclusion logic and I can see some interaction due to remove before add for new readers.

    If, that's the case. Then updating the documentation/code to block UniqueIndex and TransactionalIndexedCollection being used together.

    opened by abhishek 0
  • Support STRICT_REPLACEMENT in ObjectLockingIndexedCollection/ConcurrentIndexedCollection

    Support STRICT_REPLACEMENT in ObjectLockingIndexedCollection/ConcurrentIndexedCollection

    It would be beneficial if ConcurrentIndexedCollection and ObjectLockingIndexedCollection supported the STRICT_REPLACEMENT feature that is supported in TransactionalIndexedCollection.

    The check would not be atomic/transactional in those collections. (In fact it would be susceptible to TOCTOU issues - which would need to be mentioned in documentation for those collections).

    Rationale to do this, is that right now, the lack of this feature in all collections can cause different collections to behave differently when this feature is requested and not honoured by all collections, especially when multi-threading is not a factor. So implementing this feature will make the user experience more consistent.

    opened by npgall 0
Owner
Niall Gallagher
Niall Gallagher
JPassport works like Java Native Access (JNA) but uses the Foreign Linker API instead of JNI. Similar to JNA, you declare a Java interface that is bound to the external C library using method names.

JPassport works like Java Native Access (JNA) but uses the Foreign Linker API instead of JNI. Similar to JNA, you declare a Java interface t

null 28 Dec 30, 2022
An open source, modular alternative of sketchware. Create your own app in android using block programming like scratch!

OpenBlocks An open source, modular alternative of sketchware. Create your own app in android using block programming like scratch! What is OpenBlocks?

OpenBlocks 30 Dec 16, 2022
Trino connectors for managing cloud resources, like AWS EC2 instances or S3 buckets.

Trino connectors for managing cloud resources, like AWS EC2 instances or S3 buckets. Please keep in mind that this is not production ready and it was created for tests.

Jan Waś 11 Nov 4, 2022
FundurASM - A Assembly-like language interpreter

FundurASM - A Assembly-like language interpreter This interpreter was written by LordBurtz Licensed under GPLv2, all rights reserved Running it Downlo

null 2 Jan 31, 2022
TChart Simple and fast charts

TChart Simple and fast charts. Current version Beta 0.9.1 Preview Import jitpack.io gradle allprojects

null 30 Sep 20, 2022
A Fast, Secure, Ready to use, Highly customizable email verifier API

Email verification API What is this? A Fast, Secure, Ready to use, Highly customizable email verifier API. How to use Clone the project git clone http

Amir 3 Oct 4, 2022
Modern Java - A Guide to Java 8

Modern Java - A Guide to Java 8 This article was originally posted on my blog. You should also read my Java 11 Tutorial (including new language and AP

Benjamin Winterberg 16.1k Jan 5, 2023
icecream-java is a Java port of the icecream library for Python.

icecream-java is a Java port of the icecream library for Python.

Akshay Thakare 20 Apr 7, 2022
There are two versions of assignments(Java or C++) for the CS143-Compiler course, this repo is my Java-version solution.

Intro There are two versions of assignments(Java or C++) for the CS143-Compiler course, this repo is my Java-version solution. Course resources: This

F4DE 3 Dec 15, 2022
From Java To Kotlin - Your Cheat Sheet For Java To Kotlin

From Java To Kotlin From Java To Kotlin - Your Cheat Sheet For Java To Kotlin 中文支持 Português Español Print to Console Java System.out.print("Amit Shek

MindOrks 5.8k Dec 29, 2022
Design patterns implemented in Java

Design patterns implemented in Java Read in different language : CN, KR, FR, TR, AR Introduction Design patterns are the best formalized practices a p

Ilkka Seppälä 79k Dec 31, 2022
Feature Flags for Java made easy

✨ ✨ ✨ FF4J - Feature Flipping for Java ✨ ✨ ✨ FF4j, is an implementation of the Feature Toggle pattern. ?? Features Feature Toggle: Enable. and disable

FF4j 1.1k Dec 25, 2022
A Java to iOS Objective-C translation tool and runtime.

J2ObjC: Java to Objective-C Translator and Runtime Project site: https://j2objc.org J2ObjC blog: https://j2objc.blogspot.com Questions and discussion:

Google 5.9k Dec 29, 2022
Make Slack and Facebook Bots in Java.

JBot Make bots in Java. JBot is a java framework (inspired by Howdyai's Botkit) to make Slack and Facebook bots in minutes. It provides all the boiler

Ram 1.2k Dec 18, 2022
An in-memory file system for Java 7+

Jimfs Jimfs is an in-memory file system for Java 7 and above, implementing the java.nio.file abstract file system APIs. Getting started The latest rel

Google 2.2k Jan 3, 2023
API gateway for REST and SOAP written in Java.

Membrane Service Proxy Reverse HTTP proxy (framework) written in Java, that can be used as an API gateway as a security proxy for HTTP based integrati

predic8 GmbH 389 Dec 31, 2022
A lightweight, simple FTP server. Pure Java, no dependencies.

MinimalFTP A lightweight, simple FTP server. Pure Java, no libraries. Features Although it's named "minimal", it supports a bunch of features: 100% Ja

Guilherme Chaguri 131 Jan 5, 2023
Detect uses of legacy Java APIs

Modernizer Maven Plugin Modernizer Maven Plugin detects uses of legacy APIs which modern Java versions supersede. These modern APIs are often more per

Andrew Gaul 325 Dec 12, 2022
A lightweight command processing pipeline ❍ ⇢ ❍ ⇢ ❍ for your Java awesome app.

PipelinR PipelinR is a lightweight command processing pipeline ❍ ⇢ ❍ ⇢ ❍ for your awesome Java app. PipelinR has been battle-proven on production, as

Eduards Sizovs 288 Jan 8, 2023