If you want to differentiate null value fields from absent fields the most generic method will be using Map or JsonNode instead of POJO. POJO class has constant structure, Map or JsonNode have dynamic - contains only what you actually put there. Let's create a simple app which reads XML payload from file and creates JSON response:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.File;
import java.util.Map;
public class JsonApp {
    public static void main(String[] args) throws Exception {
        File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
        XmlMapper xmlMapper = new XmlMapper();
        Map map = xmlMapper.readValue(xmlFile, Map.class);
        ObjectMapper jsonMapper = new ObjectMapper();
        String json = jsonMapper.writeValueAsString(map);
        System.out.println(json);
    }
}
Now take a look on some examples where we test what JSON will be generated for empty, null and absent nodes.
Test 0-0
Input XML:
<Root>
    <a>A</a>
    <b>1</b>
    <c>
        <c1>Rick</c1>
        <c2>58</c2>
    </c>
</Root>
Result JSON is:
{"a":"A","b":"1","c":{"c1":"Rick","c2":"58"}}
Test 0-1
Input XML:
<Root>
    <a>A</a>
    <c>
        <c1>Rick</c1>
        <c2/>
    </c>
</Root>
Output JSON:
{"a":"A","c":{"c1":"Rick","c2":null}}
Test 0-2
Input XML:
<Root>
    <c/>
</Root>
Output JSON:
{"c":null}
The biggest problem with this simple and fast solution is we lost type information for primitives. For example, if b is Integer we should return it in JSON as number primitive which does not have quotes: " chars around. To solve this problem we should use POJO model which allows us to find all required types. Let's create POJO model for our example:
@JsonFilter("allowedFields")
class Root {
    private String a;
    private Integer b;
    private C c;
    // getters, setters
}
@JsonFilter("allowedFields")
class C {
    private String c1;
    private Integer c2;
    // getters, setters
}
We need to change our simple XML -> Map -> JSON algorithm to below one:
- Read JSON as MaporJsonNode
- Find all field names
- Create FilterProviderwith found names - notice that filter is registered withallowedFieldsname, the same which is used in@JsonFilterannotation.
- Convert MaptoPOJOfor type coercion.
- Write POJOwith filter
Simple app could look like this:
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.File;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
public class JsonApp {
    public static void main(String[] args) throws Exception {
        File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
        NodesWalker walker = new NodesWalker();
        XmlMapper xmlMapper = new XmlMapper();
        JsonNode root = xmlMapper.readValue(xmlFile, JsonNode.class);
        Set<String> names = walker.findAllNames(root);
        SimpleFilterProvider filterProvider = new SimpleFilterProvider();
        filterProvider.addFilter("allowedFields", SimpleBeanPropertyFilter.filterOutAllExcept(names));
        ObjectMapper jsonMapper = new ObjectMapper();
        jsonMapper.setFilterProvider(filterProvider);
        Root rootConverted = jsonMapper.convertValue(root, Root.class);
        String json = jsonMapper.writeValueAsString(rootConverted);
        System.out.println(json);
    }
}
class NodesWalker {
    public Set<String> findAllNames(JsonNode node) {
        Set<String> names = new HashSet<>();
        LinkedList<JsonNode> nodes = new LinkedList<>();
        nodes.add(node);
        while (nodes.size() > 0) {
            JsonNode first = nodes.removeFirst();
            if (first.isObject()) {
                ObjectNode objectNode = (ObjectNode) first;
                objectNode.fields().forEachRemaining(e -> {
                    names.add(e.getKey());
                    JsonNode value = e.getValue();
                    if (value.isObject() || value.isArray()) {
                        nodes.add(value);
                    }
                });
            } else if (first.isArray()) {
                ArrayNode arrayNode = (ArrayNode) first;
                arrayNode.elements().forEachRemaining(e -> {
                    if (e.isObject() || e.isArray()) {
                        nodes.add(e);
                    }
                });
            }
        }
        return names;
    }
}
Test 1-0
Input XML:
<Root>
    <a>A</a>
    <b>1</b>
    <c>
        <c1>Rick</c1>
        <c2>58</c2>
    </c>
</Root>
Output JSON:
{"a":"A","b":1,"c":{"c1":"Rick","c2":58}}
Test 1-1
Input XML:
<Root>
    <b>1</b>
    <c>
        <c2/>
    </c>
</Root>
Output JSON:
{"b":1,"c":{"c2":null}}
Test 1-2
Input XML:
<Root>
    <c/>
</Root>
Output JSON:
{"c":null}
After all these tests we see that dynamic checking whether field is null, empty or absent is not an easy task. Even so, above 2 solutions work for simple models you should test them for all responses you want to generate. When model is complex and contains many complex annotations such as: @JsonTypeInfo, @JsonSubTypes on Jackson side or @XmlElementWrapper, @XmlAnyElement on JAXB side it make this task very hard to implement.
I think the best solution in your example is to use @JsonInclude(NON_NULL) which send to client all set fields on XML side. null and absent should be treated on client side identically. Business logic should not rely on the fact field is set to null or absent in JSON payload.
See also: