This is a follow-up article to An approach to Semantic Modeling.
At the end of my previous article, I mentioned something about classes and concepts that I did’t cover in that article. This article is about how I decide whether a thing is an OWL Class or a SKOS Concept. Often they are both (sort of), let me elaborate!
Both OWL and SKOS describe concepts
Every single thing in a knowledge graph describes a concept. That’s the analytical philosophy of an ontology. We describe our concepts, pick our terms and set a definition. I wrote about this in a piece called A few elementary pieces on logic.
So, an owl:Class describes a concept (e.g. Animal). A skos:Concept also describes a concept (it says so in its name). There are two standards that says “this is a thing we have a word for”. But which one do we reach for?
The specifications shows that OWL and SKOS do different jobs, however. OWL is a language for describing sets of individuals and the logical conditions that belongs to those sets. SKOS is a language for describing vocabularies, the terms organisations use and how they are structured in a hierarchy, and what they mean in prose. (SKOS is actually defined by OWL.) An OWL Class is a set, a SKOS Concept is an entry in a controlled vocabulary. Keep that in mind!
My rule of thumb: will you ever instantiate it?
Will I create individuals of this, that carry their own properties?
If yes, it’s a class. If no, and what you have are a value that describes individuals or some other class, then it’s a concept.
A Person is something we will instantiate, because you and me are actual persons. We have birth dates, we work somewhere, and have plenty of other data describing us. Person is a class!
Gender, however, does not get instantiated. There’s no individual “female” walking about with properties on its own. It is a value that describes instances of Person. You can describe me as a female. It belongs into an enumeration of genders, a Concept Scheme if you like. And this Concept Scheme (enumeration/code list) contains SKOS Concepts. So, Gender is a Concept Scheme, and Female is a SKOS Concept that is a member of this Concept Scheme.
This way, we get one unique identifier for every single time we describe something (or someone) with a gender. We can give it several labels and its own definition, instead of just a plain string with no further explanation.
Let’s look into another example. These days I’m helping The Norwegian Food Safety Authority structuring a census for animals, so this is fresh in my mind.
If the knowledge graph is about individual animals, like cows, with their ear tags, birth dates, breed, gender, and so on — then it’s the class of Cattle (which will be a sub class of Animal) we instantiate. Whenever I catch myself writing a class that I realise will only appear in an enumeration, I stop and turn it into a little taxonomy instead.
ex:Animal a owl:Class ;
skos:prefLabel "Animal"@en, "Dyr"@no ;
skos:definition "An animal is a living, multicellular organism."@en .
ex:Cattle a owl:Class ;
rdfs:subClassOf ex:Animal ;
skos:prefLabel "Cattle"@en , "Storfe"@no .
ex:hasBreed a owl:ObjectProperty ;
skos:prefLabel "has breed"@en, "har rase"@no ;
rdfs:domain ex:Animal ;
rdfs:range skos:Concept . # every range to a concept value must point to skos:Concept
voc:CattleBreeds a skos:ConceptScheme ;
skos:prefLabel "Cattle breeds"@en, "Storferaser"@no .
voc:Holstein a skos:Concept ;
skos:inScheme voc:CattleBreeds ;
skos:prefLabel "Holstein"@en, "Holstein"@no ;
skos:altLabel "Holstein-Freisian"@en .
voc:Aberdeen-Angus a skos:Concept ;
skos:inScheme voc:CattleBreeds ;
skos:prefLabel "Aberdeen Angus"@en, "Abderdeen Angus"@no ;
skos:altLabel "Angus"@en .
# We can then express an actual cow as:
data:Animal-4711 a ex:Cattle ;
ex:earTag "NO-1234-5678" ;
ex:dateOfBirth "2023-04-12"^^xsd:date ;
ex:hasBreed voc:Holstein .The individual data:Animal-4711 is typed (rdf:type) to a class, and its breed is linked to a SKOS Concept.
Classes get SKOS too!
Choosing an OWL Class for ex:Cattle does not mean we can’t annotate it using SKOS. I use SKOS properties on everything in the ontologies I build, both classes and properties. Because this is way more precise than just sticking with rdfs:label and rdfs:comment. In SKOS we can annotate things with preferred label, alternative label, hidden label, and definition. We can state various notes (editorial, change, history, scope, etc.) and examples. Check out the reference for the full vocabulary. None of these resources from SKOS are solely restricted to concepts. In the SKOS vocabulary, the labels are sub-properties of rdfs:label, and the different notes don’t have any defined rdfs:domain. So these resources you can perfectly well use for classes. What you cannot borrow like this is the semantic relations (broader, narrower, related, etc.) they are restricted as resources between concepts. So, SKOS as the naming and documentation layer everywhere!
Some nations even have their own application profiles for SKOS, like the EU and Norway. In Norway, SKOS-AP-NO works as the national government standard for concept management and cataloguing.
Anyways, that was almost a digression!
The fine line
The example above draws the line between species and breed of animals, with Cattle as a class and Holstein as a concept. Why couldn’t the cattle breed Holstein just as well been a class? There’s nothing stopping you from that in RDF, and some might do it this way, and sometimes it might be the right call. It’s all about a question of need.
Will I ever have to say something about the term itself?
For breeds, the answer is almost always yes, I guess. A definition, a code from some code work, breed society, some “replaced by” pointer if two systems did merge at some point. Those are statements about a term, and SKOS has properties to describe all of them. That shows us that this thing is definitely a thing and not a string at least. But you can put SKOS annotations on a class too, so “will I ever have to same something about the term itself” isn’t really a question to distinguish classes from concepts.
Do I need reasoning over the distinctions?
If the graph must infer that an individual is a DairyCow because it’s cattle, female and lactating, then DairyCow has to be a class with class axioms. Reasoners classify individuals into classes, but they do not classify stuff into concepts. For our example with cattle breeds, we don’t need that. Since nothing is ever inferred into being a Holstein, we just record the value.
Who owns the meaning
A class hierarchy is a structure that should be stable, reviewed and versioned together with the ontology. A list of codes is content. It is maintained by domain experts, it grows and evolve more rapid than the ontology itself (usually).
This discussion isn’t new
In one of my favourite books Den logiske sandkasse (1966) by Peter Wessel Zapffe, we discover this topic through the lens of analytical philosophy. Way before RDF saw the light of day.
The concept word (term) is Cattle, while the concept is our conception of that word (the mental image of the animal).
Zapffe defines a class as an exhaustive group of phenomena. “The extension of a concept therefore always constitutes a class” (p. 91). And then he asks, as every modeller also does, “what do we want with classes when we already have concepts?” Zapffe’s answer is that we operate with classes that are not, or not yet, attached to any concept. Here we come back to how classes and concepts evolve, as previously mentioned.
Yes, “Angus” could be a class. The question is whether you have any reason to erect it and work with it, and for a competency questions only ever asking for “which breed”, you don’t. It’s just a value.
On punning
OWL 2 lets the same IRI be a class and an individual at once, so you can legally declare ex:Angus to be both an owl:Class and a skos:Concept. It’s another question if you really want to. To me, this is a sign of a postponed decision. There are many ways of modeling a domain, but be consistent.
Punning might have consequences for the instance data. Let’s say we have the breed Agnus typed both as skos:Concept and owl:Class. Some information sources might come in as rdf:type ex:Angus, while others use ex:hasBreed ex:Angus. Which means that every query has to handle both cases. Nah, pun only if you have a good explanation for why this thing has to have two lives.
You can use SKOS for what it’s worth without typing things as Concepts too, you know!
Concepts instead of string values
Things instead of strings, right? Here’s to why you should go for Concepts instead of strings, if that is what you’re considering. I’ve seen enough of properties pointing to countries as string, as example!
Concepts have unique identifiers (IRIs), so “Angus”, “Angus Aberdeen”, “Aberdeen Angus”, “ANG” is the same thing (instead of four different entries).
Concepts (and classes, for that matter) are multilingual by construction.
“breed”@en,“rase”@nb,“züchten”@deare all labels that describe one resource.Concepts can be organised, deprecated and re-parented in a SKOS vocabulary without having logical consequences for the ontology (your collection of classes).
A SHACL property shape can point to a concept scheme instead of a list of strings with
sh:in. This makes validation more precise and easier to extend.
Checklist
To summarise, here’s a checklist to guide you in what’s what.
Will I create individuals of it, with their own identity and attributes? Class
Is it a value that describes individuals of some other class? Concept
Is the list of things expected to change in a higher frequency, and is more tightly connected to the domain in subject than other things? Probably Concept
Am I about to write a class that will only appear inside an enumeration? Concept
Do I need to classify memberships using reasoning? Class
Do I need broader/narrower and mappings to external register? Concept
Still unsure? Concept, for now. It’s less painful to move things from a vocabulary to your ontology than the other way around. You can read more about this in the article Using OWL and SKOS (S. Bechhofer, A. Miles. 2008.)
Regardless, give everything skos:prefLabel and skos:definition !
About the author

Ontologist who code in C, Java and Python. Been a knowledge graph practitioner for over a decade, and had numerous talks on the topic around the globe. Knowledge Graph Specialist at Data Treehouse. Author of SHACL for the Practitioner and awarded amongst Norway’s Top 50 Women in Tech 2024.


Thanks Veronika, as usual your posts are very [use|insight]full. Do you ever create something that is both a Class and a Concept?
“Will I ever instantiate it?” is the clearest version of this test I’ve read. I ran into exactly this modeling job postings: an advertised position gets instantiated, with its own employer and date, while the title it carries behaves much more like a value in a concept scheme. “Still unsure? Concept, for now” is going on a sticky note.