Search code examples
xsdjava-8streamcxf

how effciently read soap web service response with nested objects using streams


I consider a very easy task parse a nested object asnwered by a soap webservice using Java 8 streams. Nevertheless, I am quite confused when I think about the correct or most appropriate approach to use. I know it will depend on circunstances and there will never be a simple recipe. I have been reading for the last two weeks where and how to use stream but I couldn't reach a final conclusion about few options. I put bellow four approaches I would appreciatte if someone could give technical opnion if I understood correctly the real application based on very common requirements when dealing with soap client.

I am not loking for a simple answer like "Here I do successfully this way so you can copy and paste similar idea". I am really interested to understand if I am applying properly what I have read so far.

Firstly, my nested objects answered by web service:

//first level

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "OndernemingAlgemeenType", propOrder = {
    "adressen",
    ... others properties
})
@XmlSeeAlso({
    Onderneming20Type.class
})
public class OndernemingAlgemeenType
{

    @XmlElement(name = "Adressen")
    protected AdresOndernemingLijstType adressen;
    ... others elements
}   

//second level that returns a list

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AdresOndernemingLijstType", propOrder = {
    "adres"
})
public class AdresOndernemingLijstType {

    @XmlElement(name = "Adres", required = true)
    protected List<AdresOndernemingType> adres; 
    ...
}

// third level used to filter the list and return just one AdresOndernemingType

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AdresOndernemingType", propOrder = {
    "type"
})
public class AdresOndernemingType
    extends AdresOndernemingBasisType{
    @XmlElement(name = "Type", required = true)
    protected TypeAdresOndernemingType type;
}

// fourth level

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AdresOndernemingBasisType", propOrder = {
   ... not relevant for this question
})
@XmlSeeAlso({
    AdresOndernemingType.class
})
public class AdresOndernemingBasisType
    extends AdresBasisType
{
       ... not relevant for this question
}

// fifth level and finally, the desired fields (street and city)

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AdresBasisType", propOrder = {
    "straat", //street
    "gemeente" //city
})
@XmlSeeAlso({
    AdresOndernemingDeelnemingInType.class,
    AdresOndernemingBasisType.class
})
public class AdresBasisType {

    @XmlElement(name = "Straat")
    protected StraatRR20Type straat;
    @XmlElement(name = "Gemeente")
    protected GemeenteOptioneel20Type gemeente;

// Approaches with its understanding

Approach 1: I understand that this is null exception safe. I mean, in case either getAdressen or getAdres is null there will be no exception at all and no address printed.

private void printOptionalDidactic(){
        Optional<AdresOndernemingLijstType> op = Optional.ofNullable(onderneming.getAdressen());
        op.map(AdresOndernemingLijstType::getAdres).get().stream().filter(Objects::nonNull)
        .filter(o -> "001".equals(o.getType().getCode().getValue().getValue()))
        .map(x -> System.out.println("My street is: ".concat(x.getStraat())));
}       

Approach 2: Assuming that there will be never a null element (minOccurs="1" in xsd for all) so using Optional would be pointless

private void printNoOptionalDidactic(){     
        onderneming.getAdressen().getAdres().stream()
        .filter(o -> "001".equals(o.getType().getCode().getValue().getValue()))
        .map(x -> System.out.println("My street is: ".concat(x.getStraat())));
}

Approach 3: Assuming I want to print all streets and I don't care about filtering, I understand there is no advantage at all to use flatMap before forEach Replace nested loop with Java 8 flatmap

private void printForEachDidactic(){
        onderneming.getAdressen().getAdres().stream()
        .forEach(x -> System.out.println("My street is: ".concat(x.getStraat())));
}       

Approach 4 Since no shared resource is used by the predicates and functions used in the process, I understand I could use parallelism. Nevertheless, provided that it is little data so I have no real gain on it Should I always use a parallel stream when possible?

private void printParallelDidactic(){
        onderneming.getAdressen().getAdres().parallelStream()
        .filter(o -> "001".equals(o.getType().getCode().getValue().getValue()))
        .map(x -> System.out.println("My street is: ".concat(x.getStraat())));
}

Solution

  • I wouldn't use map stream method when you really do not map elements of the stream (e.g. in approach 1 .map(x -> System.out.println...). Better call forEach, it is dedicated to execute code for each element of the stream.

    Regarding your first approach, I think there will be a NoSuchElementException thrown if onderneming.getAdressen() is null. See implementation of Optional.get:

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    

    Otherwise all approaches look valid from the code point of view. Of course I cannot say anything about your data model.

    However, you do not need to apply Java 8 streams and lambdas just because Java 8 introduced them. For example, I wouldn't use streams in your approach 3. An old school for loop will do it, too (and is slightly more readable here IMO):

    for (AdresOndernemingType x : onderneming.getAdressen().getAdres()) {
        System.out.println("My street is: ".concat(x.getStraat())));
    }
    

    Regarding parallel streams, I wouldn't use parallel() except when I really need parallelism to gain performance on huge data sets or long running tasks.