java

Why Not Supercharge Your Java App's Search with Elasticsearch?

Unlock Superior Search Capabilities: Integrate Elasticsearch Seamlessly into Your Java Applications

Why Not Supercharge Your Java App's Search with Elasticsearch?

Elasticsearch is a game changer when it comes to implementing advanced search functionalities in Java applications. It’s powerful, scalable, and really makes searching through vast amounts of data a breeze. Below, you’ll find a friendly and informal guide on how to seamlessly integrate Elasticsearch into your Java projects, so you can start benefiting from its robust search features.

First things first, you need to set up Elasticsearch. If you’re using Docker, it’s quite straightforward. Just run:

docker run -d --name elastic-test -p 9200:9200 -e "discovery.type=single-node" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:8.9.3

Once the setup is complete, you can check if everything is running smoothly by navigating your browser to http://localhost:9200/.

Next, let’s tackle the dependencies. Assuming you’re using Maven, you need to update your pom.xml file to include the Elasticsearch Java client and Jackson Databind for JSON handling:

<dependencies>
    <dependency>
        <groupId>co.elastic.clients</groupId>
        <artifactId>elasticsearch-java</artifactId>
        <version>8.15.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.0</version>
    </dependency>
</dependencies>

If you are on Gradle, just add this to your build.gradle:

dependencies {
    implementation 'co.elastic.clients:elasticsearch-java:8.15.0'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
}

Moving on to connecting to Elasticsearch; you have to create an ElasticsearchClient. Here’s a simple example to get you started:

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.ElasticsearchTransport;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClient;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;

import java.io.IOException;

public class ElasticsearchClientExample {
    public static void main(String[] args) throws IOException {
        RestClient restClient = RestClient.builder(HttpHost.create("http://localhost:9200")).build();
        ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
        ElasticsearchClient esClient = new ElasticsearchClient(transport);
    }
}

Now it’s time to make your data searchable by indexing it. Here’s an example of how to index a document:

import co.elastic.clients.elasticsearch.core.IndexResponse;
import java.io.IOException;

public class IndexDocumentExample {
    public static void main(String[] args) throws IOException {
        // Assuming you have an Elasticsearch client instance
        ElasticsearchClient esClient = new ElasticsearchClient(transport);

        Person person = new Person(30, "John Doe", new Date());
        
        IndexResponse response = esClient.index(i -> i
                .index("people")
                .id(person.getFullName())
                .document(person));

        System.out.println("Indexed with version " + response.version());
    }
}

class Person {
    private int age;
    private String name;
    private Date birthDate;

    public Person(int age, String name, Date birthDate) {
        this.age = age;
        this.name = name;
        this.birthDate = birthDate;
    }

    public String getFullName() {
        return name;
    }
}

Searching for documents is where Elasticsearch shines. Let’s start with a simple match query:

import co.elastic.clients.elasticsearch.core.search.SearchResponse;
import java.io.IOException;

public class SearchDocumentsExample {
    public static void main(String[] args) throws IOException {
        ElasticsearchClient esClient = new ElasticsearchClient(transport);
        String searchText = "John";

        SearchResponse<Person> response = esClient.search(s -> s
                .index("people")
                .query(q -> q
                        .match(t -> t
                                .field("name")
                                .query(searchText)
                        )
                ), Person.class);

        var totalHits = response.hits().total();
        if (totalHits.relation() == TotalHitsRelation.Eq) {
            System.out.println("There are " + totalHits.value() + " results");
        } else {
            System.out.println("There are more than " + totalHits.value() + " results");
        }

        response.hits().hits().forEach(hit -> {
            Person person = hit.source();
            System.out.println("Found person " + person.getFullName() + ", score " + hit.score());
        });
    }
}

If you need a more advanced search, Elasticsearch can handle fuzzy queries, perfect for dealing with typos. Check this out:

import co.elastic.clients.elasticsearch.core.search.SearchResponse;
import java.io.IOException;

public class FuzzySearchExample {
    public static void main(String[] args) throws IOException {
        ElasticsearchClient esClient = new ElasticsearchClient(transport);
        String searchText = "Jhon";

        SearchResponse<Person> response = esClient.search(s -> s
                .index("people")
                .query(q -> q
                        .match(t -> t
                                .field("name")
                                .fuzziness("AUTO")
                                .query(searchText)
                        )
                ), Person.class);

        var totalHits = response.hits().total();
        if (totalHits.relation() == TotalHitsRelation.Eq) {
            System.out.println("There are " + totalHits.value() + " results");
        } else {
            System.out.println("There are more than " + totalHits.value() + " results");
        }

        response.hits().hits().forEach(hit -> {
            Person person = hit.source();
            System.out.println("Found person " + person.getFullName() + ", score " + hit.score());
        });
    }
}

Ever thought about using search templates? These make it super easy to reuse search queries with different parameters. Here’s a sample:

import co.elastic.clients.elasticsearch.core.search.SearchTemplateResponse;
import co.elastic.clients.json.JsonData;
import java.io.IOException;

public class SearchTemplateExample {
    public static void main(String[] args) throws IOException {
        ElasticsearchClient esClient = new ElasticsearchClient(transport);

        esClient.putScript(r -> r
                .id("query-script")
                .script(s -> s
                        .lang("mustache")
                        .source("{\"query\":{\"match\":{\"{{field}}\":\"{{value}}\"}}}")
                ));

        SearchTemplateResponse<Person> response = esClient.searchTemplate(r -> r
                .index("people")
                .id("query-script")
                .params("field", JsonData.of("name"))
                .params("value", JsonData.of("John")), Person.class);

        response.hits().hits().forEach(hit -> {
            Person person = hit.source();
            System.out.println("Found person " + person.getFullName() + ", score " + hit.score());
        });
    }
}

Integrating Elasticsearch with your application means ensuring that your data-fetching routines are updated to pull from Elasticsearch instead of your database. This involves a couple of key steps:

  1. Index Your Data: Keep your data in Elasticsearch up-to-date by syncing it periodically or indexing in real-time as updates happen.

  2. Modify Your Search Service: Update your existing search service to use the Elasticsearch Java client.

  3. Handle Search Requests: Forward search requests from your UI to your search service, which then queries Elasticsearch.

  4. Display Results: Map Elasticsearch hits to your app’s data model and show them in the UI.

Here’s a simple example of a possible search service implementation:

public class SearchService {
    private final ElasticsearchClient esClient;

    public SearchService(ElasticsearchClient esClient) {
        this.esClient = esClient;
    }

    public List<Person> searchPeople(String name) throws IOException {
        SearchResponse<Person> response = esClient.search(s -> s
                .index("people")
                .query(q -> q
                        .match(t -> t
                                .field("name")
                                .query(name)
                        )
                ), Person.class);

        List<Person> people = new ArrayList<>();
        response.hits().hits().forEach(hit -> people.add(hit.source()));
        return people;
    }
}

To wrap things up, Elasticsearch can really level up your application’s search capabilities. The steps and examples here should help you get started with integration. Whether you’re managing simple search requests or tackling more complex queries, Elasticsearch promises a powerful and efficient solution to meet your needs. So dive in and explore the endless possibilities Elasticsearch offers for your Java applications!

Keywords: Elasticsearch, Java, advanced search functionalities, scalable, Docker, Maven, Gradle, indexing documents, search queries, search templates



Similar Posts
Blog Image
You’re Using Java Wrong—Here’s How to Fix It!

Java pitfalls: null pointers, lengthy switches, raw types. Use Optional, enums, generics. Embrace streams, proper exception handling. Focus on clean, readable code. Test-driven development, concurrency awareness. Keep learning and experimenting.

Blog Image
Micronaut's Non-Blocking Magic: Boost Your Java API Performance in Minutes

Micronaut's non-blocking I/O architecture enables high-performance APIs. It uses compile-time dependency injection, AOT compilation, and reactive programming for fast, scalable applications with reduced resource usage.

Blog Image
Unleash Micronaut's Power: Effortless Kubernetes Deployments for Scalable Microservices

Micronaut simplifies Kubernetes deployment with automatic descriptor generation, service discovery, scaling, ConfigMaps, Secrets integration, tracing, health checks, and environment-specific configurations. It enables efficient microservices development and management on Kubernetes.

Blog Image
Unveiling JUnit 5: Transforming Tests into Engaging Stories with @DisplayName

Breathe Life into Java Tests with @DisplayName, Turning Code into Engaging Visual Narratives with Playful Twists

Blog Image
Kickstart Your Java Magic with Micronaut and Micronaut Launch

Harnessing Micronaut Launch to Supercharge Java Development Efficiency

Blog Image
You Won’t Believe the Performance Boost from Java’s Fork/Join Framework!

Java's Fork/Join framework divides large tasks into smaller ones, enabling parallel processing. It uses work-stealing for efficient load balancing, significantly boosting performance for CPU-bound tasks on multi-core systems.