eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

1. Overview

In this tutorial, we will introduce some simple MapReduce views and demonstrate how to query them using the Couchbase Java SDK.

2. Maven Dependency

To work with Couchbase in a Maven project, import the Couchbase SDK into your pom.xml:

<dependency>
    <groupId>com.couchbase.client</groupId>
    <artifactId>java-client</artifactId>
    <version>2.7.2</version>
</dependency>

You can find the latest version on Maven Central.

3. MapReduce Views

In Couchbase, a MapReduce view is a type of index that can be used to query a data bucket. It is defined using a JavaScript map function and an optional reduce function.

3.1. The map Function

The map function is run against each document one time. When the view is created, the map function is run once against each document in the bucket, and the results are stored in the bucket.

Once a view is created, the map function is run only against newly inserted or updated documents in order to update the view incrementally.

Because the map function’s results are stored in the data bucket, queries against a view exhibit low latencies.

Let’s look at an example of a map function that creates an index on the name field of all documents in the bucket whose type field is equal to “StudentGrade”:

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.name) {    
        emit(doc.name, null);
    }
}

The emit function tells Couchbase which data field(s) to store in the index key (first parameter) and what value (second parameter) to associate with the indexed document.

In this case, we are storing only the document name property in the index key. And since we are not interested in associating any particular value with each entry, we pass null as the value parameter.

As Couchbase processes the view, it creates an index of the keys that are emitted by the map function, associating each key with all documents for which that key was emitted.

For example, if three documents have the name property set to “John Doe”, then the index key “John Doe” would be associated with those three documents.

3.2. The reduce Function

The reduce function is used to perform aggregate calculations using the results of a map function. The Couchbase Admin UI provides an easy way to apply the built-in reduce functions “_count”, “_sum”, and “_stats”, to your map function.

You can also write your own reduce functions for more complex aggregations. We will see examples of using the built-in reduce functions later in the tutorial.

4. Working With Views and Queries

4.1. Organizing the Views

Views are organized into one or more design document per bucket. In theory, there is no limit to the number of views per design document. However, for optimal performance, it has been suggested that you should limit each design document to fewer than ten views.

When you first create a view within a design document, Couchbase designates it as a development view. You can run queries against a development view to test its functionality. Once you are satisfied with the view, you would publish the design document, and the view becomes a production view.

4.2. Constructing Queries

In order to construct a query against a Couchbase view, you need to provide its design document name and view name to create a ViewQuery object:

ViewQuery query = ViewQuery.from("design-document-name", "view-name");

When executed, this query will return all rows of the view. We will see in later sections how to restrict the result set based on the key values.

To construct a query against a development view, you can apply the development() method when creating the query:

ViewQuery query 
  = ViewQuery.from("design-doc-name", "view-name").development();

4.3. Executing the Query

Once we have a ViewQuery object, we can execute the query to obtain a ViewResult:

ViewResult result = bucket.query(query);

4.4. Processing Query Results

And now that we have a ViewResult, we can iterate over the rows to get the document ids and/or content:

for(ViewRow row : result.allRows()) {
    JsonDocument doc = row.document();
    String id = doc.id();
    String json = doc.content().toString();
}

5. Sample Application

For the remainder of the tutorial, we will write MapReduce views and queries for a set of student grade documents having the following format, with grades constrained to the range 0 to 100:

{ 
    "type": "StudentGrade",
    "name": "John Doe",
    "course": "History",
    "hours": 3,
    "grade": 95
}

We will store these documents in the “baeldung-tutorial” bucket and all views in a design document named “studentGrades.” Let’s look at the code needed to open the bucket so that we can query it:

Bucket bucket = CouchbaseCluster.create("127.0.0.1")
  .openBucket("baeldung-tutorial");

6. Exact Match Queries

Suppose you want to find all student grades for a particular course or set of courses. Let’s write a view called “findByCourse” using the following map function:

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.course && doc.grade) {
        emit(doc.course, null);
    }
}

Note that in this simple view, we only need to emit the course field.

6.1. Matching on a Single Key

To find all grades for the History course, we apply the key method to our base query:

ViewQuery query 
  = ViewQuery.from("studentGrades", "findByCourse").key("History");

6.2. Matching on Multiple Keys

If you want to find all grades for Math and Science courses, you can apply the keys method to the base query, passing it an array of key values:

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourse")
  .keys(JsonArray.from("Math", "Science"));

7. Range Queries

In order to query for documents containing a range of values for one or more fields, we need a view that emits the field(s) we are interested in, and we must specify a lower and/or upper bound for the query.

Let’s take a look at how to perform range queries involving a single field and multiple fields.

7.1. Queries Involving a Single Field

To find all documents with a range of grade values regardless of the value of the course field, we need a view that emits only the grade field. Let’s write the map function for the “findByGrade” view:

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.grade) {
        emit(doc.grade, null);
    }
}

Let’s write a query in Java using this view to find all grades equivalent to a “B” letter grade (80 to 89 inclusive):

ViewQuery query = ViewQuery.from("studentGrades", "findByGrade")
  .startKey(80)
  .endKey(89)
  .inclusiveEnd(true);

Note that the start key value in a range query is always treated as inclusive.

And if all the grades are known to be integers, then the following query will yield the same results:

ViewQuery query = ViewQuery.from("studentGrades", "findByGrade")
  .startKey(80)
  .endKey(90)
  .inclusiveEnd(false);

To find all “A” grades (90 and above), we only need to specify the lower bound:

ViewQuery query = ViewQuery
  .from("studentGrades", "findByGrade")
  .startKey(90);

And to find all failing grades (below 60), we only need to specify the upper bound:

ViewQuery query = ViewQuery
  .from("studentGrades", "findByGrade")
  .endKey(60)
  .inclusiveEnd(false);

7.2. Queries Involving Multiple Fields

Now, suppose we want to find all students in a specific course whose grade falls into a certain range. This query requires a new view that emits both the course and grade fields.

With multi-field views, each index key is emitted as an array of values. Since our query involves a fixed value for course and a range of grade values, we will write the map function to emit each key as an array of the form [course, grade].

Let’s look at the map function for the view “findByCourseAndGrade“:

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.course && doc.grade) {
        emit([doc.course, doc.grade], null);
    }
}

When this view is populated in Couchbase, the index entries are sorted by course and grade. Here’s a subset of keys in the “findByCourseAndGrade” view shown in their natural sort order:

["History", 80]
["History", 90]
["History", 94]
["Math", 82]
["Math", 88]
["Math", 97]
["Science", 78]
["Science", 86]
["Science", 92]

Since the keys in this view are arrays, you would also use arrays of this format when specifying the lower and upper bounds of a range query against this view.

This means that in order to find all students who got a “B” grade (80 to 89) in the Math course, you would set the lower bound to:

["Math", 80]

and the upper bound to:

["Math", 89]

Let’s write the range query in Java:

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .startKey(JsonArray.from("Math", 80))
  .endKey(JsonArray.from("Math", 89))
  .inclusiveEnd(true);

If we want to find for all students who received an “A” grade (90 and above) in Math, then we would write:

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .startKey(JsonArray.from("Math", 90))
  .endKey(JsonArray.from("Math", 100));

Note that because we are fixing the course value to “Math“, we have to include an upper bound with the highest possible grade value. Otherwise, our result set would also include all documents whose course value is lexicographically greater than “Math“.

And to find all failing Math grades (below 60):

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .startKey(JsonArray.from("Math", 0))
  .endKey(JsonArray.from("Math", 60))
  .inclusiveEnd(false);

Much like the previous example, we must specify a lower bound with the lowest possible grade. Otherwise, our result set would also include all grades where the course value is lexicographically less than “Math“.

Finally, to find the five highest Math grades (barring any ties), you can tell Couchbase to perform a descending sort and to limit the size of the result set:

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .descending()
  .startKey(JsonArray.from("Math", 100))
  .endKey(JsonArray.from("Math", 0))
  .inclusiveEnd(true)
  .limit(5);

Note that when performing a descending sort, the startKey and endKey values are reversed, because Couchbase applies the sort before it applies the limit.

8. Aggregate Queries

A major strength of MapReduce views is that they are highly efficient for running aggregate queries against large datasets. In our student grades dataset, for example, we can easily calculate the following aggregates:

  • number of students in each course
  • sum of credit hours for each student
  • grade point average for each student across all courses

Let’s build a view and query for each of these calculations using built-in reduce functions.

8.1. Using the count() Function

First, let’s write the map function for a view to count the number of students in each course:

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.course && doc.name) {
        emit([doc.course, doc.name], null);
    }
}

We’ll call this view “countStudentsByCourse” and designate that it is to use the built-in “_count” function. And since we are only performing a simple count, we can still emit null as the value for each entry.

To count the number of students in the each course:

ViewQuery query = ViewQuery
  .from("studentGrades", "countStudentsByCourse")
  .reduce()
  .groupLevel(1);

Extracting data from aggregate queries is different from what we’ve seen up to this point. Instead of extracting a matching Couchbase document for each row in the result, we are extracting the aggregate keys and results.

Let’s run the query and extract the counts into a java.util.Map:

ViewResult result = bucket.query(query);
Map<String, Long> numStudentsByCourse = new HashMap<>();
for(ViewRow row : result.allRows()) {
    JsonArray keyArray = (JsonArray) row.key();
    String course = keyArray.getString(0);
    long count = Long.valueOf(row.value().toString());
    numStudentsByCourse.put(course, count);
}

8.2. Using the sum() Function

Next, let’s write a view that calculates the sum of each student’s credit hours attempted. We’ll call this view “sumHoursByStudent” and designate that it is to use the built-in “_sum” function:

function (doc, meta) {
    if(doc.type == "StudentGrade"
         && doc.name
         && doc.course
         && doc.hours) {
        emit([doc.name, doc.course], doc.hours);
    }
}

Note that when applying the “_sum” function, we have to emit the value to be summed — in this case, the number of credits — for each entry.

Let’s write a query to find the total number of credits for each student:

ViewQuery query = ViewQuery
  .from("studentGrades", "sumCreditsByStudent")
  .reduce()
  .groupLevel(1);

And now, let’s run the query and extract the aggregated sums into a java.util.Map:

ViewResult result = bucket.query(query);
Map<String, Long> hoursByStudent = new HashMap<>();
for(ViewRow row : result.allRows()) {
    String name = (String) row.key();
    long sum = Long.valueOf(row.value().toString());
    hoursByStudent.put(name, sum);
}

8.3. Calculating Grade Point Averages

Suppose we want to calculate each student’s grade point average (GPA) across all courses, using the conventional grade point scale based on the grades obtained and the number of credit hours that the course is worth (A=4 points per credit hour, B=3 points per credit hour, C=2 points per credit hour, and D=1 point per credit hour).

There is no built-in reduce function to calculate average values, so we’ll combine the output from two views to compute the GPA.

We already have the “sumHoursByStudent” view that sums the number of credit hours each student attempted. Now we need the total number of grade points each student earned.

Let’s create a view called “sumGradePointsByStudent” that calculates the number of grade points earned for each course taken. We’ll use the built-in “_sum” function to reduce the following map function:

function (doc, meta) {
    if(doc.type == "StudentGrade"
         && doc.name
         && doc.hours
         && doc.grade) {
        if(doc.grade >= 90) {
            emit(doc.name, 4*doc.hours);
        }
        else if(doc.grade >= 80) {
            emit(doc.name, 3*doc.hours);
        }
        else if(doc.grade >= 70) {
            emit(doc.name, 2*doc.hours);
        }
        else if(doc.grade >= 60) {
            emit(doc.name, doc.hours);
        }
        else {
            emit(doc.name, 0);
        }
    }
}

Now let’s query this view and extract the sums into a java.util.Map:

ViewQuery query = ViewQuery.from(
  "studentGrades",
  "sumGradePointsByStudent")
  .reduce()
  .groupLevel(1);
ViewResult result = bucket.query(query);

Map<String, Long> gradePointsByStudent = new HashMap<>();
for(ViewRow row : result.allRows()) {
    String course = (String) row.key();
    long sum = Long.valueOf(row.value().toString());
    gradePointsByStudent.put(course, sum);
}

Finally, let’s combine the two Maps in order to calculate GPA for each student:

Map<String, Float> result = new HashMap<>();
for(Entry<String, Long> creditHoursEntry : hoursByStudent.entrySet()) {
    String name = creditHoursEntry.getKey();
    long totalHours = creditHoursEntry.getValue();
    long totalGradePoints = gradePointsByStudent.get(name);
    result.put(name, ((float) totalGradePoints / totalHours));
}

9. Conclusion

We have demonstrated how to write some basic MapReduce views in Couchbase, and how to construct and execute queries against the views, and extract the results.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.

You can learn more about MapReduce views and how to query them in Java at the official Couchbase developer documentation site.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)