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 – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

In this tutorial, we’ll first understand what a triple is and then discuss how to store triple elements in a Java ArrayList.

2. What Is a Triple?

We may have heard about the Pair type, which always contains two values, for example, a key-value association. A triple is pretty similar to a pair. The only difference is a triple always has three values instead of two. For example, a 3D coordinate can be considered a triple structure: x=-100L, y=0L, z=200L.

In the 3D coordinate example, the three values in the triple have the same type: Long. However, the three value types in a triple aren’t necessarily the same. For example, name=”Lionel Messi”, birthday=24 June 1987 (Date), number=10 is another triple structure of football players. In this example, the three values in the triple have different types: String, Date, and Integer.

Next, we’ll see a more detailed triple example and discuss the proper way to store the triple objects in an ArrayList.

3. The Example: an Arithmetic Question Generator

Let’s say we want to build an arithmetic question generator for pupils. For example, “100 + 200 = ?” is one question. It’s composed of the first number, the second number, and an operator. Thus, we have three values. We’ll store these three parts as triples.

Also, we’ve defined the accepted operators as an Enum:

enum OP {
    PLUS("+"), MINUS("-"), MULTIPLY("x");
    final String opSign;
                                         
    OP(String x) {
        this.opSign = x;
    }
}

As we can see, we only accept three operators.

The question generation logic is pretty straightforward. But first, let’s create a method to generate the questions from the three parts:

String createQuestion(Long num1, OP operator, Long num2) {
    long result;
    switch (operator) {
        case PLUS:
            result = num1 + num2;
            break;
        case MINUS:
            result = num1 - num2;
            break;
        case MULTIPLY:
            result = num1 * num2;
            break;
        default:
            throw new IllegalArgumentException("Unknown operator");
    }
    return String.format("%d %s %d = ? ( answer: %d )", num1, operator.opSign, num2, result);
}

If we have values in the triple structure in a list, we can pass the three values to the method above and create questions. For simplicity, we’ll use unit test assertions to verify if the expected questions can be created. Let’s say we expect to generate three questions by triples:

List<String> EXPECTED_QUESTIONS = Arrays.asList(
    "100 - 42 = ? ( answer: 58 )",
    "100 + 42 = ? ( answer: 142 )",
    "100 x 42 = ? ( answer: 4200 )");

Now, it’s time to consider the central problem: how to store the triple structure in a list?

Of course, if we only focus on this problem, we could create a QuestionInput class containing the three arguments. However, our goal is to generally store triple structures in a list, which means our solution should solve the “question generation” problem and work for “3D coordinate” and “football player” examples too.

Usually, two ideas may come up to store triples in a list:

  • List<List> – Storing the three values in a list or array (we take List as an example in the tutorial) and nesting the list in the outer list: List<List>
  • List<Triple<…>> – Creating a generic Triple class

So next, let’s see them in action. Further, we’ll discuss their pros and cons.

4. Triples as Lists

We know we can add elements of any type to a raw list. So next, let’s see how to store triples as lists.

4.1. Storing a Triple Structure as a List of Three Elements

For each triple structure, we can create a list. Then add the three values to the list. So next, let’s store the math question triples as lists:

List myTriple1 = new ArrayList(3);
myTriple1.add(100L);
myTriple1.add(OP.MINUS);
myTriple1.add(42L);

List myTriple2 = new ArrayList(3);
myTriple2.add(100L);
myTriple2.add(OP.PLUS);
myTriple2.add(42L);

List myTriple3 = new ArrayList(3);
myTriple3.add(100L);
myTriple3.add(OP.MULTIPLY);
myTriple3.add(42L);

List<List> listOfTriples = new ArrayList<>(Arrays.asList(myTriple1, myTriple2, myTriple3));

As the code above shows, we create three raw ArrayList objects to carry the three triples. In the end, we add the three raw lists to the outer list listOfTriples.

4.2. A Word About Type Safety

Raw usage of the lists allows us to put values in different types to the list, for example, Long and OP. Therefore, this approach can be used for any triple structure.

However, on the other hand, we lost the type safety when we used a list in raw. An example can explain it quickly:

List oopsTriple = new ArrayList(3);
oopsTriple.add("Oops");
oopsTriple.add(911L);
oopsTriple.add("The type is wrong");

listOfTriples.add(oopsTriple);
assertEquals(4, listOfTriples.size());

As we can see, we’ve created the oopsTriple list carrying a different triple structure. Also, listOftriples accepts it without any complaints when we add oopsTriple to it.

So now, listOfTriples contains two different kinds of triples: Long, OP, Long and String, Long, String. Therefore, when we use the triples in the listOfTriples list, we must check if the triple is in the expected type.

4.3. Using Triples in the List

Now that we understand the pros and cons of the “triples as lists” approach, let’s see how to use the triples in listOfTriples to generate arithmetic questions:

List<String> questions = listOfTriples.stream()
    .filter(
        triple -> triple.size() == 3
          && triple.get(0) instanceof Long
          && triple.get(1) instanceof OP
          && triple.get(2) instanceof Long
    ).map(triple -> {
        Long left = (Long) triple.get(0);
        String op = (String) triple.get(1);
        Long right = (Long) triple.get(2);
        return createQuestion(left, op, right);
    }).collect(Collectors.toList());

assertEquals(EXPECTED_QUESTIONS, questions);

As the code snippet above shows, we’ve used Java Stream API‘s map() to transform the list of triples into a list of generated questions. However, as the type of the inner raw lists isn’t guaranteed, we must check if the elements in each raw list conform to Long, OP, and Long. So, before we call the map() method, we’ve called filter() to skip unrecognized triples, such as the String, Long, and String one.

Further, because of the list’s raw usage, no contract can guarantee that the three elements in the raw list are in types: Long, OP, and Long. Therefore, we must explicitly cast the list elements to the desired types before passing them to the createQuestion() method.

If we run the test, it passes. So this approach solves our problem. Its advantage is obvious. We can store any triple structures in a list without creating new classes. But we lost the type safety. Therefore, we must perform type checks before we use the raw values. Moreover, we must cast the values to the desired types.

Imagine that we use this approach on many different triple structures in our application, then we have to put a lot of effort into type-checking and casting. It makes the code less readable, hard to maintain, and error-prone. Therefore, this approach isn’t recommended.

5. Creating a Generic Triple Class

Now that we understand the pros and cons of the “triples as lists” approach, let’s try to find a straightforward and type-safe way to store triples in a list.

5.1. The Generic Triple Class

One benefit Java generics brings is type-safety. So next, let’s create a generic Triple class:

public class Triple<L, M, R> {

    private final L left;
    private final M middle;
    private final R right;

    public Triple(L left, M middle, R right) {
        this.left = left;
        this.middle = middle;
        this.right = right;
    }

    public L getLeft() {
        return left;
    }

    public M getMiddle() {
        return middle;
    }

    public R getRight() {
        return right;
    }
}

The class is pretty straightforward. In this example, we’ve made Triple immutable. If it’s required to be mutable, we can remove the final keywords and add the corresponding setters.

5.2. Initializing Triples and Storing in a List

Next, let’s create three triple objects and add them to the listOfTriples List:

Triple<Long, OP, Long> triple1 = new Triple<>(100L, OP.MINUS, 42L);
Triple<Long, OP, Long> triple2 = new Triple<>(100L, OP.PLUS, 42L);
Triple<Long, OP, Long> triple3 = new Triple<>(100L, OP.MULTIPLY, 42L);

List<Triple<Long, OP, Long>> listOfTriples = new ArrayList<>(Arrays.asList(triple1, triple2, triple3));

As we can see, as our generic Triple class has type parameters, there are no raw usages in the code above. Furthermore, the type is safe.

Next, let’s test what happens if we create an invalid Triple object and attempt to add it to the list:

Triple<String, Long, String> tripleOops = new Triple<>("Oops", 911L, "The type is wrong");
listOfTriples.add(tripleOops);

If we add the two lines above, the code doesn’t compile:

java: incompatible types: 
com...Triple<...String, ...Long, ...String> cannot be converted to com...Triple<...Long, ...OP, ...Long>

Therefore, this type-safe approach protects us from falling into the type-error trap.

5.3. Using the Triple Elements

As the type is safe, we can directly use the values without performing any type-checking and type-casting:

List<String> questions = listOfTriples.stream()
  .map(triple -> createQuestion(triple.getLeft(), triple.getMiddle(), triple.getRight()))
  .collect(Collectors.toList());

assertEquals(EXPECTED_QUESTIONS, questions);

The test passes if we give it a run. Compared to the “triples as lists” approach, the code above is cleaner and much more readable.

6. Conclusion

In this article, we’ve explored how to store triples in a list by examples. We’ve discussed why we should create a generic Triple type instead of using triples as lists.

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.
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.

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