Since you need to flatten the stream data, i.e. turn each element of type OffenceEntity into a group of elements, mapToInt() isn't the right operation. For that purpose you can use flatMap() (or its flavor flatMapToInt()).
To differentiate between the two operations map() and flatMap(), remember a simple principle:
- when you need one-to-one transformation, use map()or it's flavors
- for one-to-many transformation - flatMap()or it's flavors.
Since Java 16 we can also utilize mapMulty() to flatten the data in the stream. But it's rather a special purpose tool than a common option. Apart from flattening this operation allows to filter out stream elements so in contrast to flatMap() which turns an element into 1+ (one or more) elements, mapMulty() produces 0+ (zero or more) elements.
flatMap()
flatMap() expects as an argument a function, that takes an element and produces a stream (which is also important distinction between map and flatMap).
After flattening stream data, we need to filter OffenceDetailsEntity objects having a suitable date and extract penaltyPoints. Which can be done using filter() + mapToInt().
public int countPenaltyPointsTest(Set<OffenceEntity> offences, LocalDate date) {
    
    return offences.stream()                                // Stream<OffenceEntity>
        .flatMap(offence -> offence.getDetails().stream())  // Stream<OffenceDetailsEntity>
        .filter(ode -> date.isAfter(ode.getStartDate())     // Stream<OffenceDetailsEntity>
                        && date.isBefore(ode.getEndDate()))
        .mapToInt(OffenceDetailsEntity::getPenaltyPoints)   // IntStream
        .sum();
}
mapMulty()
This operation allows incorporating imperative programming features (i.e. loops and conditional statements) into the stream pipeline.
As mentioned before, it's a special purpose tool with a lot of peculiarities which should be applied mindfully.
Here's quote from the API Note regurding when to use mapMulty():
This method is preferable to flatMap in the following circumstances:
- When replacing each stream element with a small (possibly zero) number of elements. Using this method avoids the overhead of creating
a new Streaminstance for every group of result elements, as required
byflatMap.
- When it is easier to use an imperative approach for generating result elements than it is to return them in the form of a Stream.
For instance, mapMulty() is capable to substitute a combination flatMap() + filter(), or multiple flatMap() operations.
It expects an argument of type BiConsumer, i.e. a consumer, which in turn takes two arguments: stream element and a consumer of the resulting type. Each value offered to the consumer becomes a new stream element, replacing the initial element.
Here's how this method can be implemented using mapMultyToInt():
public int countPenaltyPointsTest(Set<OffenceEntity> offences, LocalDate date) {
    
    return offences.stream()
        .mapMultiToInt((offence, consumer) -> 
            offence.getDetails().forEach(ode -> {
                if (date.isAfter(ode.getStartDate()) && date.isBefore(ode.getEndDate()))
                    consumer.accept(ode.getPenaltyPoints());
        }))
        .sum();
}
For more examples and information on how to use mapMulty(), have a look at this question.