re
run
.
me

Building CXF REST Service in OSGi for Karaf

I’ll leave it to the experts to tell how awesome OSGi is. Among the many benefits, I could tell you why we picked up OSGi for a pet project - Modularity, avoiding JAR hell and dynamic updates (hey, why not?)

We chose Apache Felix (an OSGi framework specification implementation) and Apache Karaf (ummm, how do I put this - something like an app server for OSGi applications). Besides serving as an OSGi container, Karaf has a lot of awesome features (pun intended). And we like the idea of managing multiple Karaf instances managed through Zookeeper.

Enough talk, let’s see some code. This is a rudimentary tutorial on how to run a basic OSGi CXF-JAX Rest Service on Karaf. Given a REST parameter (a name), the service would just return Hello, (name). That’s it !! The entire project could be downloaded here

So, if the request is http://localhost:8181/cxf/karafsimple/say/hello/arun, the response would be Hello, arun

Please note that this project does not have any domain models and therefore has only two projects - one for the REST (Controller) and the other for the actual service implementation.

The project structure looks like this :

Step 1 - Service Implementation

This project just has two ‘useful’ things - the HelloService interface and the HelloServiceImpl class.

HelloService
1
2
3
4
5
6
package me.rerun.karafcxf.service.impl;

public interface HelloService {

    public String sayHello(String name);
}
HelloServiceImpl
1
2
3
4
5
6
7
8
9
10
package me.rerun.karafcxf.service.impl;

public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
        return "Hello, "+name;
    }

}

Stupid right?

Step 2 - REST project

Similar to the Service project, this project also has just two notable things - the HelloRestService interface and the HelloRestServiceImpl class.

HelloRestService

HelloRestService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package me.rerun.karafcxf.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

//Maps for the `say` in the URL

@Path("say")
public interface HelloRestService {

    @GET
    @Path("hello/{name}") //Maps for the `hello/John` in the URL
    public String handleGet(@PathParam("name") String name);

}

HelloRestServiceImpl

The HelloRestServiceImpl does nothing but calls the HelloService which gets injected through Blueprint Dependency Injection. Hey, does the inject look very familiar to Spring DI? Exactly !! The Blueprint DI is heavily influenced by Spring DI. In fact, the original work for blueprint is done by Spring.

HelloRestServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package me.rerun.karafcxf.rest;

import me.rerun.karafcxf.service.impl.HelloService;

public class HelloRestServiceImpl implements HelloRestService{


    //Just like Spring.  Please add Getters/Setters. Blueprint annotations are still work in progress
    private HelloService helloService;


    public String handleGet(String name){

        return helloService.sayHello(name);

    }


    /*
        Constructor
     */

    public HelloRestServiceImpl(){
    }

    /*
        Getters and Setters
     */

    public HelloService getHelloService() {
        return helloService;
    }

    public void setHelloService(HelloService helloService) {
        this.helloService = helloService;
    }

}

Step 3 - Injections

XMLs (of any name) inside OSGI-INF/blueprint folder will get picked up for DI scanning.

serviceimpl.xml

Does two things in one tag :

  1. Registers the HelloService into the service registry for lookup
  2. Says that its implementation is HelloServiceImpl
resources/OSGI-INF/blueprint/serviceimpl.xml
1
2
3
4
5
6
7
8
9
10
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">


    <service ref="helloServiceBean" interface="me.rerun.karafcxf.service.impl.HelloService">
        <bean class="me.rerun.karafcxf.service.impl.HelloServiceImpl"/>
    </service>

</blueprint>

In fact, you could do this in two separate steps. More on that here.

resources/OSGI-INF/blueprint/serviceimpl.xml
1
2
3
4
<service id="helloServiceBean" ref="helloServiceImpl"
    interface="me.rerun.karafcxf.service.impl.HelloService" />

<bean id="helloServiceImpl" class="me.rerun.karafcxf.service.impl.HelloServiceImpl" />

rest.xml

resources/OSGI-INF/blueprint/rest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
           xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"
           xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
           xmlns:cxf="http://cxf.apache.org/blueprint/core"
           xsi:schemaLocation="
  http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
  http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd
  http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
  http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">
<!-- 1 -->
    <cxf:bus id="cxfBus1">
        <cxf:features>
            <cxf:logging/>
        </cxf:features>
    </cxf:bus>

<!-- 2 -->
    <jaxrs:server address="/karafsimple" id="someRestService">
        <jaxrs:serviceBeans>
            <ref component-id="restServiceImpl"/>
        </jaxrs:serviceBeans>
    </jaxrs:server>

<!-- 3 -->
    <!-- Implementation of the rest service -->
    <bean id="restServiceImpl" class="me.rerun.karafcxf.rest.HelloRestServiceImpl">
        <property name="helloService" ref="helloServiceBean"/>  <!--Points to the reference below -->
    </bean>

<!-- 4 -->
    <!-- This has to point to the service registered through serviceimpl.xml in the service.impl project -->

    <reference id="helloServiceBean"
               interface="me.rerun.karafcxf.service.impl.HelloService">
    </reference>

</blueprint>

1) cxf-bus is the bus configuration for CXF. It is like the manager for all CXF services. The most common use as far as I know is to configure custom interceptors (for auditing, request/response manipulation, headers manipulation etc)

2) the jaxrs:server initiates a server which would start listening to the URLs that we mapped for. Of course, we would want to map the handlers for the URLs and those go under the serviceBeans.

3) The third note in the XML is the just the restServiceImpl configuration and the injection of the helloService as a property inside the HelloRestServiceImpl

4) The reference tag will lookup the service registry for a bean with the same id.

Step 4 - KAR - Karaf Archive (Optional but easier this way)

Technically, you could just start throwing the bundles generated through the Maven into to the deploy directory of karaf. However, as the project goes big and your dependencies are becoming many, it is advisable to use the .kar archive. Picture .kar as your .war or .ear bundle. A .kar archive, internally, looks something like this :

Building .kar

Building the .kar is composed of two steps :

Step 1

feature.xml

Similar to your web descriptor or application descriptor, we have the features descriptor in Karaf to represent the entire repository that we are bundling together to compose our application.

In our case, we don’t have any external .jar dependencies except for the cxf and the http service for which we are using the in-built features available inside karaf.

feature.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<features xmlns="http://karaf.apache.org/xmlns/features/v1.0.0"
          name="${project.artifactId}-${project.version}">
    <feature name="karafcxf" description="karaf cxf hello" version="1.0-SNAPSHOT" resolver="(obr)">
        <details>${project.description}</details>
        <feature>http</feature>
        <feature>cxf</feature>
        <feature>this-project-dependants</feature>

        <bundle>mvn:karafcxf/karafcxf.service.impl/1.0-SNAPSHOT</bundle>
        <bundle>mvn:karafcxf/karafcxf.rest/1.0-SNAPSHOT</bundle>
    </feature>


    <feature name="this-project-dependants">
        <!-- Dependent bundles if you have any. This project doesn't have one
        <bundle>mvn:org.apache.commons/commons-lang3/3.1</bundle>
        <bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.5</bundle>
        <bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.5</bundle>
        <bundle>mvn:org.codehaus.jackson/jackson-jaxrs/1.9.5</bundle>
        <bundle>mvn:com.google.guava/guava/14.0.1</bundle>
        <bundle>wrap:mvn:org.apache.httpcomponents/httpcore/4.2.4</bundle>
        <bundle>wrap:mvn:org.apache.httpcomponents/httpmime/4.2.5</bundle>
        <bundle>wrap:mvn:org.noggit/noggit/0.5</bundle>
        <bundle>wrap:mvn:org.apache.solr/solr-solrj/4.4.0</bundle>
        -->
    </feature>


</features>

Step 2

Maven plugin configuration to create .kar, which indicates where your feature.xml is located (Note that this file is located inside your karaf project)

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>features-maven-plugin</artifactId>
                <version>2.3.2</version>
                <executions>
                    <execution>
                        <id>create-kar</id>
                        <goals>
                            <goal>create-kar</goal>
                        </goals>
                        <configuration>
                            <featuresFile>${project.basedir}/src/main/resources/feature.xml</featuresFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Wraps

Notice the wrap protocol in front of the mvn protocol in few bundles as in

<bundle>wrap:mvn:org.apache.httpcomponents/httpmime/4.2.5</bundle>

Not all Jars are OSGi ready but they would obviously be used as a dependency in our project. In those cases, Karaf notices the wrap protocol and bundles the JAR into an OSGi bundle. In case of doubt, just drop off the wrap and Karaf would complain that the jar is not OSGi compatible. (Alternatively, you could open up each of the dependant jars and check their manifest files)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
013-08-28 01:38:48,669 | WARN  | raf-2.3.2/deploy | KarArtifactInstaller             | eployer.kar.KarArtifactInstaller  192 | 24 - org.apache.karaf.deployer.kar - 2.3.2 | Unable to install Kar feature xx-xxx-xxxxxx/0.0.0
org.osgi.framework.BundleException: Jar is not a bundle, no Bundle-SymbolicName mvn:org.apache.httpcomponents/httpcore/4.2.4
  at org.apache.karaf.features.internal.FeaturesServiceImpl.installBundleIfNeeded(FeaturesServiceImpl.java:836)[26:org.apache.karaf.features.core:2.3.2]
  at org.apache.karaf.features.internal.FeaturesServiceImpl.doInstallFeature(FeaturesServiceImpl.java:618)[26:org.apache.karaf.features.core:2.3.2]
  at org.apache.karaf.features.internal.FeaturesServiceImpl.installFeatures(FeaturesServiceImpl.java:414)[26:org.apache.karaf.features.core:2.3.2]
  at org.apache.karaf.features.internal.FeaturesServiceImpl.installFeature(FeaturesServiceImpl.java:402)[26:org.apache.karaf.features.core:2.3.2]
  at Proxy508d2419_d21e_4a93_b7fb_26e28d2f03a6.installFeature(Unknown Source)[:]
  at org.apache.karaf.deployer.kar.KarArtifactInstaller.installFeatures(KarArtifactInstaller.java:189)[24:org.apache.karaf.deployer.kar:2.3.2]
  at org.apache.karaf.deployer.kar.KarArtifactInstaller.install(KarArtifactInstaller.java:134)[24:org.apache.karaf.deployer.kar:2.3.2]
  at org.apache.karaf.deployer.kar.KarArtifactInstaller.update(KarArtifactInstaller.java:348)[24:org.apache.karaf.deployer.kar:2.3.2]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.update(DirectoryWatcher.java:1103)[6:org.apache.felix.fileinstall:3.2.6]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.update(DirectoryWatcher.java:898)[6:org.apache.felix.fileinstall:3.2.6]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.process(DirectoryWatcher.java:482)[6:org.apache.felix.fileinstall:3.2.6]
  at org.apache.felix.fileinstall.internal.DirectoryWatcher.run(DirectoryWatcher.java:291)[6:org.apache.felix.fileinstall:3.2.6]

Changing Log Levels

For all our development environment, we would want to increase our log level to get more feedback from Karaf. This could be achieved by modifying the org.ops4j.pax.logging.cfg file located in your <karaf-installation-directory>/etc

Step 5 - Other Maven configuration

Not technically a step because we would have covered this from the beginning anyway. And nothing fancy here.

Parent pom.xml

  1. Has the rest of the sub-modules configured.
  2. The various library dependencies configured
pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>karafcxf</groupId>
    <artifactId>karafcxf</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <cxf.version>2.7.5</cxf.version>
    </properties>

    <modules>
        <module>karafcxf.rest</module>
        <module>karafcxf.service.impl</module>
        <module>karafcxf.kar</module>
    </modules>


    <dependencies>
      
  

Rest and ServiceImpl pom.xml

The other poms.xmls aren’t interesting

They just affiliate themselves to the parent pom with the parent tag as in

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.3.7</version>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                        <Bundle-Version>${project.version}</Bundle-Version>
                        <Bundle-Activator>com.nutraspace.coreservices.search.rest.Activator</Bundle-Activator>
                        <Export-Package>com.nutraspace.coreservices.search.rest*;version=${project.version}</Export-Package>
                        <Import-Package>*</Import-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>

Step 6 - Bring up Karaf

My Karaf installation is located at : /Users/Gabriel/apps/apache-karaf-2.3.2

Start Karaf :

<installation-dir>/bin/./karaf

Installing CXF and HTTP services

features:chooseurl cxf 2.7.5

features:install http cxf

Checking whether your bundle is installed and your service running

osgi:list

dxf:list-endpoints

Stop Karaf

Ctrl +D (Please note that Ctrl +C closes the connection abruptly instead of stopping Karaf)

In case of accidental Ctrl+C and if Karaf isn’t starting properly, do a

rm -rf <installation-dir>/data/cache/*

URL Mapping

Like I mentioned earlier, the target request URL will be something like http://localhost:8181/cxf/karafsimple/say/hello/arun and the response would be Hello, arun

1) The HelloRestService interface has all the JAX RS annotations for the URL mapping. Well, technically, the interfaces just maps for say/hello/(name)

2) The karafsimple in the URL is derived from the the JAX RS address in blueprint.xml (about that later in Step 3 - rest.xml).

3) The cxf is a default if you deploy a CXF service on Karaf which obviously you could change.

Quicksorting - 3-way and Dual Pivot

It’s no news that Quicksort is considered one of the most important algorithms of the century and that it is the defacto system sort for many languages, including the Arrays.sort in Java.

So, what’s new about quicksort?

Well, nothing except that I figured just now (after 2 damn years of release of Java 7) that the Quicksort implementation of the Arrays.sort has been replaced with a variant called Dual-Pivot QuickSort. This thread is not only awesome for this reason but also how humble Jon Bentley and Joshua Bloch really are.

What did I do next?

Just like everybody else, I wanted to implement it and do some benchmarking - against some 10 million integers (random and duplicate).

Oddly enough, I found the following results :

Random Data :

  • Quick Sort Basic : 1222 millis
  • Quick Sort 3 Way : 1295 millis (seriously !!)
  • Quick Sort Dual Pivot : 1066 millis

Duplicate Data :

  • Quick Sort Basic : 378 millis
  • Quick Sort 3 Way : 15 millis
  • Quick Sort Dual Pivot : 6 millis

Stupid Question 1 :

I am afraid that I am missing something in the implementation of 3-way partition. Across several runs against random inputs (of 10 million) numbers, I could see that the single pivot always performs better (although the difference is less than 100 milliseconds for 10 million numbers).

I understand that the whole purpose of making the 3-way Quicksort as the default Quicksort until now is that it does not give 0(n2) performance on duplicate keys - which is very evident when I run it against duplicate input. But is it true that for the sake of handling duplicate data, a small penalty is taken by 3-way? Or is my implementation bad?

Stupid Question 2

My Dual Pivot implementation (link below) does not handle duplicates well. It takes a sweet forever (0(n2)) to execute. Is there a good way to avoid this? Referring to the Arrays.sort implementation, I figured out that ascending sequence and duplicates are eliminated well before the actual sorting is done. So, as a dirty fix, if the pivots are equal I fast-forward the lowerIndex until it is different than pivot2. Is this a fair implementation?

1
2
3
4
5
6
7
 else if (pivot1==pivot2){
        while (pivot1==pivot2 && lowIndex<highIndex){
            lowIndex++;
            pivot1=input[lowIndex];
        }
    }

That’s it. That is all I did?

I always find the tracing of the algorithm interesting but with the number of variables in Dual Pivot quicksort, my eyes found it overwhelming while debugging. So, I also went ahead and created trace-enabled implementations (for all 3) so that I could see where the variable pointers currently are.

These trace-enabled classes just overlay where the pointers are below the array values. I hope you find these classes useful.

eg. for a Dual Pivot iteration

Where can you download the code?

The entire project (along with a few lame implementations of DSA) is available on github here. The quicksort classes alone could be found here.

Here’s my implementation of the SinglePivot (Hoare), 3-way (Sedgewick) and the new Dual-Pivot (Yaroslavskiy)

Single Pivot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package basics.sorting.quick;

import static basics.sorting.utils.SortUtils.exchange;
import static basics.sorting.utils.SortUtils.less;
import basics.shuffle.KnuthShuffle;

public class QuickSortBasic {

  public void sort (int[] input){
      
      //KnuthShuffle.shuffle(input);
      sort (input, 0, input.length-1);
  }

  private void sort(int[] input, int lowIndex, int highIndex) {

      if (highIndex<=lowIndex){
          return;
      }
      
      int partIndex=partition (input, lowIndex, highIndex);

      sort (input, lowIndex, partIndex-1);
      sort (input, partIndex+1, highIndex);
  }

  private int partition(int[] input, int lowIndex, int highIndex) {
      
      int i=lowIndex;
      int pivotIndex=lowIndex;
      int j=highIndex+1;
      
      
      while (true){
          
          while (less(input[++i], input[pivotIndex])){
              if (i==highIndex) break;
          }
          
          while (less (input[pivotIndex], input[--j])){
              if (j==lowIndex) break;
          }
              
          if (i>=j) break;
          
          exchange(input, i, j);
          
      }
      
      exchange(input, pivotIndex, j);
      
      return j;
  }
  
}

3-way

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package basics.sorting.quick;

import static basics.shuffle.KnuthShuffle.shuffle;
import static basics.sorting.utils.SortUtils.exchange;
import static basics.sorting.utils.SortUtils.less;

public class QuickSort3Way {
  
  
  public void sort (int[] input){
      //input=shuffle(input);
      sort (input, 0, input.length-1);
  }

  public void sort(int[] input, int lowIndex, int highIndex) {
      
      
      if (highIndex<=lowIndex) return;
      
      int lt=lowIndex;
      int gt=highIndex;
      int i=lowIndex+1;
      
      int pivotIndex=lowIndex;
      int pivotValue=input[pivotIndex];
      
      
      while (i<=gt){
          
          
          if (less(input[i],pivotValue)){
              exchange(input, i++, lt++);
          }
          else if (less (pivotValue, input[i])){
              exchange(input, i, gt--);
          }
          else{
              i++;
          }
              
          
      }
      
      sort (input, lowIndex, lt-1);
      sort (input, gt+1, highIndex);
      
      
  }

}

Dual Pivot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package basics.sorting.quick;

import static basics.shuffle.KnuthShuffle.shuffle;
import static basics.sorting.utils.SortUtils.exchange;
import static basics.sorting.utils.SortUtils.less;

public class QuickSortDualPivot {

  public void sort (int[] input){
      //input=shuffle(input);
      sort (input, 0, input.length-1);
  }

  private void sort(int[] input, int lowIndex, int highIndex) {

      if (highIndex<=lowIndex) return;
      
      int pivot1=input[lowIndex];
      int pivot2=input[highIndex];
      
      
      if (pivot1>pivot2){
          exchange(input, lowIndex, highIndex);
          pivot1=input[lowIndex];
          pivot2=input[highIndex];
          //sort(input, lowIndex, highIndex);
      }
      else if (pivot1==pivot2){
          while (pivot1==pivot2 && lowIndex<highIndex){
              lowIndex++;
              pivot1=input[lowIndex];
          }
      }
      

      int i=lowIndex+1;
      int lt=lowIndex+1;
      int gt=highIndex-1;
      
      while (i<=gt){
          
          if (less(input[i], pivot1)){
              exchange(input, i++, lt++);
          }
          else if (less(pivot2, input[i])){
              exchange(input, i, gt--);
          }
          else{
              i++;
          }
          
      }
      
      
      exchange(input, lowIndex, --lt);
      exchange(input, highIndex, ++gt);
      
      sort(input, lowIndex, lt-1);
      sort (input, lt+1, gt-1);
      sort(input, gt+1, highIndex);
          
  }

}

Your’s Deeply - Why Arrays.deepEquals When We Have Arrays.equals

While everybody would naturally accept the following lines of code on grounds of reference equality and value equality and that String and wrappers override the equals method, it takes some effort at first to accept the behavior of Arrays.equals and Arrays.deepEquals

Reference and Value equality
1
2
3
4
5
6
7
8
9
10
11
Object obj1=new Object();
Object obj2=new Object();
      
String hello1=new String("hello");
String hello2=new String("hello");
      
System.out.println(hello1.equals(hello2)); //returns true
System.out.println(hello1==hello2); //returns false
      
System.out.println(obj1.equals(obj2)); //returns false
System.out.println(obj1==obj2); //returns false

Boring Stuff :-(

First, the similarities

Both Arrays.equals and Arrays.deepEquals are similar in certain behaviors as

  1. If they are the same object (reference equality), they return true
  2. If either of the compared objects are null, then return false
  3. If the array lengths are not equal, then return false
  4. They care about order (position)

Next, the differences

Arrays.equals is really just skin deep

Opening up the souce, we could see that the lousy Array.equals just does this

Arrays.equals
1
2
3
4
5
6
7
8
 for (int i=0; i<length; i++) {
     Object o1 = a[i];
     Object o2 = a2[i];

     if (!(o1==null ? o2==null : o1.equals(o2)))
         return false;
 }

So, it just loops through the given arrays, does a equals on each of the pairs. This means that if you are passing in a String/Wrapper array or any other arrays whose equals method is overridden, then they are equal. Non-equals-overridden classes (derived from Object) will return false.

Arrays.deepEquals looks really deep

From the source, we could understand that Arrays.deepEquals

  1. Loops through the input arrays, gets each pair
  2. Analyses the type of each pair
  3. Delegates the equal deciding logic to one of the overloaded Arrays.equals if they are one of the primitive arrays
  4. Delegates recursively to Arrays.deepEquals if it is an Object array
  5. Calls the respective object’s equals, for any other object
Arrays.deepEquals
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for (int i = 0; i < length; i++) {
            Object e1 = a1[i];
            Object e2 = a2[i];

            if (e1 == e2)
                continue;
            if (e1 == null)
                return false;

            // Figure out whether the two elements are equal
            boolean eq;
            if (e1 instanceof Object[] && e2 instanceof Object[])
                eq = deepEquals ((Object[]) e1, (Object[]) e2);
            else if (e1 instanceof byte[] && e2 instanceof byte[])
                eq = equals((byte[]) e1, (byte[]) e2);
           
           
            else
                eq = e1.equals(e2);

            if (!eq)
                return false;
        }
        return true;

Now, the Awesome stuff !!

Equals not overridden (nested and non-nested)

While doing an Arrays.equals for nested or non-nested ‘non-overridden equals’ objects, it is safe to assume that if Arrays.equals return false, then Arrays.deepEquals also return false.

Non-Nested

So, given two arrays

Non-overridden Non-nested
1
2
3
private YourClass[] equalsNotOverriddenArrayNonNested1={new YourClass(), new YourClass()};
private YourClass[] equalsNotOverriddenArrayNonNested2={new YourClass(), new YourClass()};
  

where YourClass is simply

YourClass.java
1
2
public class YourClass {
}

then the following assertions are true

Non-overridden Non-nested assertions
1
2
assertFalse(Arrays.equals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2));
assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNonNested1, equalsNotOverriddenArrayNonNested2));

Nested

Also given two arrays,

Non-overridden Nested
1
2
private Object[] equalsNotOverriddenArrayNested1={new YourClass(), new YourClass[]{new YourClass()}};
private Object[] equalsNotOverriddenArrayNested2={new YourClass(), new YourClass()};

the following assertions are true

Non-overridden Nested assertions
1
2
3
 assertFalse(Arrays.equals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2));
  assertFalse(Arrays.deepEquals(equalsNotOverriddenArrayNested1, equalsNotOverriddenArrayNested2));
  

Equals overridden

Non-Nested

While doing an Arrays.equals for non-nested ‘overridden equals’ objects, it can be said that if Arrays.equals is true, then Arrays.deepEquals also return true.

Given two String arrays

Overridden Non-nested
1
2
private Object[] equalsOverriddenArrayNonNested1={"101","201"};
private Object[] equalsOverriddenArrayNonNested2={"101","201"};

the following assertions are true

Overridden Non-nested assertions
1
2
assertTrue(Arrays.equals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));
assertTrue(Arrays.deepEquals(equalsOverriddenArrayNonNested1, equalsOverriddenArrayNonNested2));

since they are just one-to-one equals call on each pair.

Nested

Interesting scenario : While doing an Arrays.equals for nested ‘overridden equals’ objects, if Arrays.equals is false, then Arrays.deepEquals need not be false.

Consider two Object arrays which has two values - a String and a String array

Overridden Nested
1
2
private Object[] equalsOverriddenArrayNested1={new String("hello"), new String[]{new String("hello")}};
private Object[] equalsOverriddenArrayNested2={new String("hello"), new String[]{new String("hello")}};

you can notice that the Arrays.equals return false while Arrays.deepEquals return true

Overridden Nested assertions
1
2
assertFalse(Arrays.equals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2));
assertTrue(Arrays.deepEquals(equalsOverriddenArrayNested1, equalsOverriddenArrayNested2));

The result for Arrays.deepEquals is logical since each (from the source), the method loops through each pair of elements, checks whether it is an array type and calls deepEquals on each of the pair. If it is an non-array type, then it just calls the equals on the object.

However, the result of Arrays.equals is tricky but at the same time obvious. The Arrays.equals method blindly calls equals on each pair and since the second arguments String[] are of Object type (whose equals is not overridden), it checks for reference equality and fails !!

The entire testcase can be found below :