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

1. Introduction

A perfect number is a special type of positive integer that holds a unique property as it is equal to the sum of its proper divisors, excluding the number itself.

One of the most intriguing aspects of perfect numbers is their rarity. Perfect numbers are infrequent within the realm of integers. In fact, throughout history, only a handful of perfect numbers have been identified. The first four perfect numbers known to humanity are 6, 28, 496, and 8128.

In this article, we’ll delve into the concept of perfect numbers and explore various approaches to check if a given number falls into this intriguing category.

2. Understanding Perfect Numbers

Let’s examine the first few examples to grasp the concept of perfect numbers. The number 6, for instance, has proper divisors 1, 2, and 3. Summing these divisors yields 1 + 2 + 3 = 6, making 6 a perfect number. Similarly, the number 28 has proper divisors 1, 2, 4, 7, and 14, with their sum equating to 1 + 2 + 4 + 7 + 14 = 28. Hence, 28 is another perfect number.

We can employ different methodologies to determine whether a given number is perfect. Let’s explore three common approaches.

3. Brute Force Method

One way to check for perfection is by iterating through all possible divisors of the number and summing them. If the sum equals the number itself, we have a perfect number. This method can be implemented using a loop that traverses from 1 to n/2, where n is the given number. For each divisor found, we accumulate the sum. Finally, we compare the sum with the original number to determine perfection:

public static boolean isPerfectBruteForce(int number) {
    int sum = 0;
    for (int i = 1; i <= number / 2; i++) {
        if (number % i == 0) {
            sum += i;
        }
    }
    return sum == number;
}

The choice to iterate up to n/2 is based on the observation that the largest possible proper divisor of a number n, excluding n itself, is n/2. Divisors larger than n/2 would result in a quotient less than 2, which is not considered a proper divisor. This approach ensures that we cover all the necessary divisors efficiently.

The brute force method iterates through all possible divisors from 1 to n/2 and calculates the sum. The time complexity is linear concerning the input number.

4. Stream-based Method

We can also utilize Java Streams to check for perfect numbers. In this method, we generate a stream of divisors from 2 to the square root of the number being tested (rounded down), filter the divisors that evenly divide the number, and then calculate the sum by adding each divisor and its corresponding pair (number/test). Finally, we compare the sum with the original number to determine perfection:

public static boolean isPerfectStream(int number) {
    int sum = IntStream.rangeClosed(2, (int) Math.sqrt(number))
      .filter(test -> number % test == 0)
      .reduce(1, (s, test) -> s + test + (number / test));
    return sum == number;
}

When searching for divisors of a number, we can stop iterating once we reach the square root of the number. This is because factors always come in pairs. For example, if a is a divisor of a number, then b = number/a is also a divisor. If a is less than or equal to the square root of the number, then b will be greater than or equal to the square root of the number. Therefore, by finding divisors up to the square root, we have covered all possible factors.

The stream-based method uses Java Streams to filter and sum the divisors up to the square root of the number. By only looping up to the square root, we can reduce the amount of work needed to find the divisors and improve efficiency. This optimized approach significantly reduces the execution time compared to the previous attempts, making it a more efficient solution for checking perfect numbers.

5. Euclid-Euler Theorem

A more efficient method, based on the Euclid-Euler theorem, allows us to identify perfect numbers without iterating through all possible divisors. According to the theorem, if 2^(p-1) * (2^p – 1) is a prime number, then (2^p – 1) * 2^(p-1) is a perfect number. Here, p must be a prime number. By utilizing the theorem, we can focus on calculating perfect numbers based on the formula. This approach allows us to efficiently identify perfect numbers without the need to iterate through all possible divisors or explicitly find prime numbers:

public static boolean isPerfectEuclidEuler(int number) {
    int p = 2;
    int perfectNumber = (int) (Math.pow(2, p - 1) * (Math.pow(2, p) - 1));
    while (perfectNumber <= number) {
        if (perfectNumber == number) {
            return true;
        }
        p++;
        perfectNumber = (int) (Math.pow(2, p - 1) * (Math.pow(2, p) - 1));
    }
    return false;
}

The Euclid-Euler theorem method leverages prime numbers and calculates perfect numbers based on the theorem. The time complexity of the Euclid-Euler method is better than that of the brute force and stream-based methods because it only requires checking numbers up to log n.

Although the time complexity is better than the brute force and Stream-based methods, the execution time of the Euclid-Euler method we provided may not meet expectations. One reason for this suboptimal performance is the usage of Math.pow(), which is known for its slowness. Considering that we’re only calculating the power of base 2, we can improve the efficiency by utilizing binary shift operations instead of Math.pow():

public static boolean isPerfectEuclidEulerUsingShift(int number) {
    int p = 2;
    int perfectNumber = (2 << (p - 1)) * ((2 << p) - 1);
    while (perfectNumber <= number) {
        if (perfectNumber == number) {
            return true;
        }
        p++;
        perfectNumber = (2 << (p - 1)) * ((2 << p) - 1);
    }
    return false;
}

By replacing Math.pow() with binary shift operations (2 << p) for calculating powers of 2, we can achieve faster and more efficient calculations. This optimization significantly improves the performance of the Euclid-Euler method.

6. Analysis and Comparison

Let’s analyze and compare the three different methods we provided for checking perfect numbers.

The brute force method, although simple, iterates through all possible divisors and compares the sum with the original number. It is suitable for smaller numbers but becomes less efficient as the input size increases. This method has a time complexity linear to the input number, resulting in slower execution times for larger numbers.

The stream-based method offers a more concise and expressive implementation by utilizing Java Streams to filter and sum the divisors efficiently. By limiting the iteration to the square root of the number, it reduces unnecessary calculations and iterations, improving efficiency and reducing execution time. The stream-based method strikes a balance between simplicity and efficiency.

The Euclid-Euler theorem method provides the most efficient approach among the three. Leveraging the theorem’s formula allows us to calculate perfect numbers directly without checking all possible divisors. This method significantly reduces the computational effort and achieves remarkable efficiency. It is particularly effective for large numbers, as it avoids iterating through numerous divisors.

To compare the performance of these methods, we conducted benchmarking using JMH, a common tool for micro benchmarking Java programs. The benchmark was performed using a known perfect number, specifically 33550336. The results of the benchmark are:

Benchmark                                               Mode  Cnt         Score         Error  Units
PerfectNumberBenchmark.bruteForceBenchmark             thrpt    5        55.070 ±       2.674  ops/s
PerfectNumberBenchmark.streamBenchmark                 thrpt    5     96114.246 ±    3666.451  ops/s
PerfectNumberBenchmark.euclidEulerBenchmark            thrpt    5    144639.676 ±    3409.540  ops/s
PerfectNumberBenchmark.euclidEulerUsingShiftBenchmark  thrpt    5  99191865.954 ± 5924410.475  ops/s

The “Cnt” column represents the number of iterations performed for each benchmark mode. The “Score” column represents the throughput or the number of operations per second achieved by each benchmark. It indicates how often a particular operation or algorithm was executed within a second. The “Error” column represents the statistical error associated with the benchmark results. It provides an estimate of the variability or uncertainty in the measured values. A smaller error indicates more consistent and reliable results.

The Euclid-Euler method stands out as the most efficient approach, followed by the stream-based method. The brute force method, while straightforward, is less efficient and more suitable for smaller numbers. When dealing with larger numbers, the Euclid-Euler or stream-based methods are recommended for optimal performance.

7. Conclusion

In this article, we explored the fascinating concept of perfect numbers and examined various approaches to determine if a given number falls into this category.

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)