Classes, Properties, and Inheritance
Defining types, relationships, and how inheritance works, through the lens of OOP.
If you've used any object-oriented language, you already know the concepts on this page. RDF has classes, properties, inheritance, and instances. The syntax is different, but the ideas are the same.
The examples on this page use the following prefixes:
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix vocab: <https://example.org/vocab/> .
@prefix people: <https://example.org/people/> .
@prefix projects: <https://example.org/projects/> .Classes
A class defines a type of thing. In Java you'd write class Person {}. In RDF:
vocab:Person a rdfs:Class .
vocab:Project a rdfs:Class .
vocab:Department a rdfs:Class .Creating an instance is like calling new. In Java: Person alice = new Person(). In RDF:
people:alice a vocab:Person .
projects:q3 a vocab:Project .The a keyword is shorthand for rdf:type. It says "this resource is an instance of this class."
Inheritance
In Java:
class Person {}
class Employee extends Person {}
class Contractor extends Person {}In RDF:
vocab:Person a rdfs:Class .
vocab:Employee a rdfs:Class ;
rdfs:subClassOf vocab:Person .
vocab:Contractor a rdfs:Class ;
rdfs:subClassOf vocab:Person .rdfs:subClassOf is extends. An Employee is a kind of Person. A Contractor is a kind of Person.
This means every Employee is automatically also a Person. You don't have to state both. If you create an Employee:
people:alice a vocab:Employee .The system knows people:alice is also a vocab:Person. A script for all Persons returns Alice:
PREFIX vocab: <https://example.org/vocab/>
SELECT ?person WHERE {
?person a vocab:Person .
}
# Returns alice (Employee), plus any Contractors, plus direct PersonsThis is the same as instanceof in Java. alice instanceof Person is true because Employee extends Person.
Multi-Level Hierarchies
Inheritance is transitive, just like in OOP:
class Task {}
class BugReport extends Task {}
class CriticalBug extends BugReport {}vocab:Task a rdfs:Class .
vocab:BugReport a rdfs:Class ;
rdfs:subClassOf vocab:Task .
vocab:CriticalBug a rdfs:Class ;
rdfs:subClassOf vocab:BugReport .A CriticalBug is a BugReport and a Task. Select Tasks and you get all three levels.
Properties
Properties are like fields on a class. They define what facts can be stated about an instance.
In Java:
class Person {
String name;
String email;
Person manages;
}In RDF, properties are declared separately from classes:
vocab:name a rdf:Property ;
rdfs:domain vocab:Person ;
rdfs:range xsd:string .
vocab:email a rdf:Property ;
rdfs:domain vocab:Person ;
rdfs:range xsd:string .
vocab:manages a rdf:Property ;
rdfs:domain vocab:Person ;
rdfs:range vocab:Project .rdfs:domain says which class the property belongs to (like which class the field is declared on). rdfs:range says what type the value is (like the field's type).
Then you use them:
people:alice
vocab:name "Alice Chen" ;
vocab:email "alice@example.com" ;
vocab:manages projects:q3 .A Key Difference from OOP
In OOP, you can't add a name field to something that isn't a Person. The compiler rejects it. In RDF, you can use any property on any resource. If you write projects:q3 vocab:email "project@example.com", RDF won't reject it. Instead, the system will infer that projects:q3 must be a vocab:Person (because vocab:email has domain vocab:Person).
This is useful but can be surprising. Domain and range are declarations ("emails belong to people"), not constraints ("reject non-people with emails"). For strict enforcement, use SHACL.
Property Inheritance
Properties can also have hierarchies:
vocab:dueDate a rdf:Property .
vocab:criticalDeadline a rdf:Property ;
rdfs:subPropertyOf vocab:dueDate .If a project has a critical deadline, the system infers it also has a due date with the same value. A script for vocab:dueDate returns both. Think of it as: a critical deadline is a specific kind of due date.
Labels and Descriptions
Give your classes and properties human-readable names and descriptions:
vocab:Person a rdfs:Class ;
rdfs:label "person" ;
rdfs:comment "An individual in the organization." .
vocab:email a rdf:Property ;
rdfs:label "email address" ;
rdfs:comment "The person's primary email." .rdfs:label is the display name. rdfs:comment is a description. These make the data self-describing: anyone (or any system) reading the graph can understand what things mean.
Putting It Together
A small vocabulary for an organization, with OOP equivalents in comments:
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix vocab: <https://example.org/vocab/> .
# class Person {}
vocab:Person a rdfs:Class ;
rdfs:label "person" .
# class Employee extends Person {}
vocab:Employee a rdfs:Class ;
rdfs:subClassOf vocab:Person ;
rdfs:label "employee" .
# class Project {}
vocab:Project a rdfs:Class ;
rdfs:label "project" .
# String name; (on Person)
vocab:name a rdf:Property ;
rdfs:domain vocab:Person ;
rdfs:range xsd:string ;
rdfs:label "name" .
# String email; (on Person)
vocab:email a rdf:Property ;
rdfs:domain vocab:Person ;
rdfs:range xsd:string ;
rdfs:label "email" .
# Project manages; (on Person)
vocab:manages a rdf:Property ;
rdfs:domain vocab:Person ;
rdfs:range vocab:Project ;
rdfs:label "manages" .Three types, three properties, with inheritance. If you know OOP, you know this.
See Also
- Individuals: specific named instances (like enum values or constants)
- SHACL: strict constraints on data (like compile-time type checking)
- Turtle Syntax: the full syntax reference for
.ttlfiles