Expand my Community achievements bar.

July 31st AEM Gems Webinar: Elevate your AEM development to master the integration of private GitHub repositories within AEM Cloud Manager.
SOLVED

javax.jcr.ValueFormatException: Can not assign multiple values to single valued property - property set dynamically via sling servlet

Avatar

Level 5

Hi all,

 

i am trying to populate a node (nt:unstructured) with properties and values taken from a json file.

i am getting issues in populating arraylist of strings to the jcr node. The below error is being reported

javax.jcr.ValueFormatException: Can not assign multiple values to single valued property:

The property set on last line of the below code creates single-valued property and not multi-valued as i want.

 

Resource resource = request.getResourceResolver().getResource(aemPath);
Node node = resource.adaptTo(Node.class);
Value[] values = new Value[ArrayList.size()];
node = resource.adaptTo(Node.class);
Value[] values = new Value[ArrayList.size()];
ValueFactory valueFactory = node.getSession().getValueFactory();
for(int index=0; index<ArrayList.size(); index++){
values[index] = valueFactory.createValue(ArrayList.get(index));
}
node.setProperty(aemFieldName, values);

@aanchal-sikka, @Jennifer_Dungan, @yuhuisg, @EstebanBustamante, @Harwinder-singh, @arunpatidar, @Rohan_Garg

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

Your code looks correct but for some reason the property being set is classified as single type even though values array is being passed to it.

One minor point I can take out is as per Adding_And_Writing_Properties

setProperty(String name, Value[] values);
The property type of the property will be that specified by the node type of this node.
If the property type of the supplied Value objects is different from that required, then a best-effort conversion is attempted
If the conversion fails, a ValueFormatException is thrown.

 

I am not sure what will make a forced conversion in this case. Can you try the below snip once ?

Property property = node.setProperty(aemFieldName, values);
if (!property.isMultiple()) {
property.remove();
}

 

View solution in original post

9 Replies

Avatar

Community Advisor

Hello @aem_noob 

 

We shouldn't use Node API, until absolutely necessary.

 

  • For setting values we should use ModifiableValueMap 

Following blog shares snippets about getting and setting properties (single & multi-valued)

https://aem-ti2fap.blogspot.com/2021/08/use-cases-of-modifiablevaluemap-in-aem.html

 

To get ModifiableValueMap, just adapt resource to it

    ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);

 

  • If one only needs to get Values, then ValueMap should be used.

 


Aanchal Sikka

Avatar

Level 5

In my situation the property does not exist on the JCR just yet. Will ModifiableValueMap allow to add properties on the fly?

Also, Is there any particular reason for not using Node API directly?
I am aware that using it hides the resource abstraction provided by Sling but other than Sling APIs will anyways all JCR APIs behind the scene so this should work either ways.

Avatar

Community Advisor

@aem_noob 

 

Why use Resource API:

Simply put, Resources offer various APIs that provide default values and handle exceptions more effectively when a value or child does not exist. These APIs closely align with how AEM developers typically code, resulting in more manageable and less fragile code. You would notice benefits on level of:

  1. Adapting and Reusing Resource-centric APIs (Page/Assets/tags etc)
  2. Exception handling
  3. Accessing properties/hierarchy
  4. Event handling

 

For more details, please refer to thread https://experienceleaguecommunities.adobe.com/t5/adobe-experience-manager/node-vs-resource-in-aem/m-...

 

Multiple people have shared there thoughts here.

 

Examples of modifiableValueMap areavailable on the link

https://aem-ti2fap.blogspot.com/2021/08/use-cases-of-modifiablevaluemap-in-aem.html

 

To get ModifiableValueMap, just adapt resource to it

    ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);

 

Before setting the property, check this

 

String[] val = {"test1","test2"};

if(modifiableValueMap.get("someproperty").getClass().isArray()){
   modifiableValueMap.put("property", val);
} else {
   modifiableValueMap.remove("property");
   modifiableValueMap.put("property", val );
}
modifiableValueMap.commit();

 

 


Aanchal Sikka

Avatar

Community Advisor

Hi @aem_noob,

The problem is that you are trying to change single value filed into multi-value field. This will not work, and using ModifiableValueMap will not change that. You will end up with similar exception (JCR api will be called behind the scene anyway).

This is also explained in the JCR documentation:

To solve the issue, you will have to first remove single value field, and next you can replace it with multi-value, so your code can look like this:

Resource resource = request.getResourceResolver().getResource(aemPath);
Node node = resource.adaptTo(Node.class);
Value[] values = new Value[ArrayList.size()];
node = resource.adaptTo(Node.class);
Value[] values = new Value[ArrayList.size()];
ValueFactory valueFactory = node.getSession().getValueFactory();

for (int index=0; index<ArrayList.size(); index++) {
  values[index] = valueFactory.createValue(ArrayList.get(index));
}

// Checking if property is multi-value, if not removing property, so it can be set again but as a multi-value
Property property = node.getProperty(aemFieldName);
if (!property.isMultiple()) {
  property.remove();
}

node.setProperty(aemFieldName, values);

If you will decide to refactor you code and use Sling api (ModifiableValueMap), you will have to remove property as well to make it work.

Avatar

Level 5

@lukasz-m- This throws a null pointer exception -

Property property = node.getProperty(aemFieldName);

aemFieldName is not existing on the JCR just yet - Its being read from JSON and then put into JCR.
When trying to do it directly via setProperty it throws value format exception

node.setProperty(aemFieldName, values);

Avatar

Correct answer by
Community Advisor

Your code looks correct but for some reason the property being set is classified as single type even though values array is being passed to it.

One minor point I can take out is as per Adding_And_Writing_Properties

setProperty(String name, Value[] values);
The property type of the property will be that specified by the node type of this node.
If the property type of the supplied Value objects is different from that required, then a best-effort conversion is attempted
If the conversion fails, a ValueFormatException is thrown.

 

I am not sure what will make a forced conversion in this case. Can you try the below snip once ?

Property property = node.setProperty(aemFieldName, values);
if (!property.isMultiple()) {
property.remove();
}

 

Avatar

Level 5

@Rohan_Garg- Thanks a lot, this works

The below sets single valued property

 node.setProperty(aemFieldName, values);

this sets multi-valued property.

Property property = node.setProperty(aemFieldName, values);

why is the property casting causing the difference?

Avatar

Community Advisor

Not sure exactly about that but here's my best guess -

Without casting to Property type the node.setProperty() works on best-effort conversion - This might very well be the number of properties set via above statement - If most of the properties you are setting are single lined values then stands to reason the best effort will evaluate like its a single lined value.

Try setting all multi-lined properties and use this without Property type!

Avatar

Administrator

@aem_noob Do you find the suggestions from users useful? Please let us know if more information is required. Otherwise, please mark the answer as correct for posterity. 



Kautuk Sahni